windows-rs: UNICODE_STRING contains a PWSTR that points to a non-null-terminated string
Which crate is this about?
windows-sys
Crate version
0.48.0
Summary
Per UNICODE_STRING documentation on MSDN (emphasis added):
Buffer
Pointer to a wide-character string. Note that the strings returned by the various LSA functions might not be null-terminated.
Windows 7, Windows Server 2008, Windows Vista, Windows Server 2003 and Windows XP: When the Length structure member is zero and the MaximumLength structure member is 1, the Buffer structure member can be an empty string or contain solely a null character. This behavior changed beginning with Windows Server 2008 R2 and Windows 7 with SP1.
And STRING documentation on MSDN (emphasis added):
If the string is null-terminated, Length does not include the terminating NULL.
(ie: the value may not be null terminated)
At the moment, the UNICODE_STRING::Buffer
member is declared as a PWSTR
:
And STRING::Buffer
is a PSTR
:
PWSTR
assumes it is pointing to a null-terminated array, and uses wcslen
to find the length:
PSTR
does the same but with strlen
:
Both of these functions cannot check the Length
, so have buffer overrun issues:
Security Note These functions incur a potential threat brought about by a buffer overrun problem. Buffer overrun problems are a frequent method of system attack, resulting in an unwarranted elevation of privilege. For more information, see Avoiding buffer overruns.
Toolchain version/configuration
% rustup show Default host: x86_64-apple-darwin rustup home: ~/.rustup
installed toolchains
stable-x86_64-apple-darwin (default) nightly-x86_64-apple-darwin
installed targets for active toolchain
thumbv6m-none-eabi wasm32-unknown-unknown x86_64-apple-darwin x86_64-pc-windows-gnu <-- using this target x86_64-unknown-linux-gnu
active toolchain
stable-x86_64-apple-darwin (default) rustc 1.70.0 (90c541806 2023-05-31)
Reproducible example
use windows::core::{PSTR, PWSTR};
use windows::Win32::Foundation::UNICODE_STRING;
use windows::Win32::System::Kernel::STRING;
fn main() {
let mut hello = [104u16, 101, 108, 108, 111, 33];
let u = UNICODE_STRING {
Length: 10,
MaximumLength: 12,
Buffer: PWSTR(hello.as_mut_ptr()),
};
let mut world = [119u8, 111, 114, 108, 100, 33];
let s = STRING {
Length: 5,
MaximumLength: 6,
Buffer: PSTR(world.as_mut_ptr()),
};
// These are unsafe conversions because they don't use the Length parameter
println!("{}", unsafe { u.Buffer.to_string().unwrap() });
println!("{}", unsafe { s.Buffer.to_string().unwrap() });
// Use length parameter
let u = String::from_utf16(unsafe { std::slice::from_raw_parts(u.Buffer.0, u.Length.into() / 2) })
.unwrap();
let s = std::str::from_utf8(unsafe { std::slice::from_raw_parts(s.Buffer.0, s.Length.into()) })
.unwrap();
println!("{u} {s}");
}
Crate manifest
[package]
name = "windows-strings"
version = "0.1.0"
edition = "2021"
[dependencies]
windows = { version = "0.48.0", features = ["Win32_System_Kernel", "Win32_Foundation"] }
Expected behavior
- the
Buffer
field should be a*mut
pointer, not aPSTR
/PWSTR
. STRING
andUNICODE_STRING
should be wrapped similar to what this crate does forHSTRING
to provide safe conversions to RustString
types
because using Buffer.to_string()
with these is not correct!
Actual behavior
Additional comments
No response
EDITED: fixed code example’s UNICODE_STRING::Length
to be a length in bytes, rather than UTF-16 codepoints, and extended MaximumLength
. Added note about raw pointers.
About this issue
- Original URL
- State: closed
- Created a year ago
- Comments: 22 (6 by maintainers)
@riverar Well, no.
PWSTR
’s have the_Null_terminated_
SAL annotation. They are always assumed to be null terminated. That’s in contrast toPWCH
, a type that aliases the same underlying type, but doesn’t have the_Null_terminated_
attribute.This is how (I presume) things are designed. Not going to comment on how things turn out in practice. YMMV, I guess.
On a more personal note, while I have your attention already: Love your contributions, here, and elsewhere. You’re an absolute animal! Much like Kenny, who’s someone/“something” I cannot name without falling victim to blasphemy.
Thank you both. You’ve provided me with powerful tools that make my life easier, and a feeling of confidence, that it’s not just me who “give[s] a damn”.
SetEnvironmentStringsW
(and friends) are interesting in a number of ways:_NullNull_terminated_
annotation. Ignoring the misnomer this is roughly correct.It’s the metadata transformation that uncovers a few issues with the current approach. The obvious one up front:
[NotNullTerminated]
AND[NullNullTerminated]
creates a conflict that cannot be resolved. One could argue that[NotNullTerminated]
is to only mean “might not be null-terminated”, but really, things are wrong at a much more fundamental level.Specifically, the implied assumption that
PWSTR
is null-terminated, but without a dedicated[NullTerminated]
attribute, introduced the need for the awkward negative attribute[NotNullTerminated]
for thePWCH
type.A more tractable scheme would be as follows:
PWCH
as the unconstrained character pointer type, and expose it.[NullTerminated]
attribute for allPWSTR
s.[NullNullTerminated]
attribute, expose thePZZWSTR
type with that attribute, or pick up the attribute where it happens to be spelled out in the function signature (e.g.SetEnvironmentStringsW
).With those changes the transformed signature would look like this:
Are there any issues with this proposed change, and would the win32metadata team be willing to act upon it (assuming that riddle isn’t going to make Windows.Win32.winmd obsolete at some point)?
@tim-weis I hope to share more about those plans as soon as I can. Thanks for your patience.
Kenny is currently heads down working on riddle to simplify WinRT and Win32 component authoring and code generation. We are actively exploring other potential areas where riddle could be used but we there’s nothing to share yet. (Literally. I’m not trying to be evasive.)
Regarding contributions to the metadata repositories: I’d say yes, please do continue. Anything done in those repositories will certainly serve as a baseline for any potential future work and benefits all projections, not just Rust.
Fair.
PWSTR
holds a pointer to a null-terminated string. It having unsafe utility functions is not a bug.UNICODE_STRING
contains aPWSTR
that points to a non-null-terminated string, breaking its contract.Maybe a short term fix would be to pick up on
[NotNullTerminated]
annotatedPWSTR
s and replace them with*mut _
.On the metadata side, perhaps we can then extend the
[NotNullTerminated]
attribute to include a parameter that indicates which field member can be relied upon for the string length. (Or just re-use[NativeArrayInfo]
or[MemorySize]
, it’s not clear yet.) This should aid codegen in determining whether to provide a nicer facility or to emit the raw pointer.Some other APIs and structures that make use of non-null-terminated
PWSTR
s (from metadata’s perspective) that we need to keep in mind: