-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
env: properly handle nulls in REG_SZ strings #16190
Conversation
strip_trailing_null(accountName); | ||
strip_trailing_null(userDomain); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My intention was to just remove these two lines here, but then I realized that it doesn't actually seem to do anything, because HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
contains a USERNAME
value (with the value-value of SYSTEM
) which immediately overwrites this. Reading more into volatile environment variables, I've realized that this code can be replaced with one that simply copies them from the stale (process) environment block, because they are immutable across a login.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
clever. this means it is no longer a clone of RegenerateUserEnvironment
, but that's OK.
src/inc/til/env.h
Outdated
// The difference is that we don't | ||
// * handle autoexec.bat (intentionally unsupported). | ||
// * call LookupAccountSidW to get the USERNAME/USERDOMAIN variables | ||
// These are available as standard environment variables and they're "volatile" | ||
// (= computed dynamically at each login and then constant until logout). | ||
// We don't expect our process to impersonate another user so we can simplify this. | ||
// * call GetComputerNameW to get the COMPUTERNAME variable, for the same reason. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This description of "volatile" seems a bit off, if it covers CLIENTNAME
, which I'd expect to change if the user reconnects to the same Remote Desktop session from a different client computer.
I have assumed that the "HKEY_CURRENT_USER\Volatile Environment" key is created with RegCreateKeyExW REG_OPTION_VOLATILE or the NT API equivalent, so that it is never saved to the user profile, but I haven't verified whether this is actually true.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's basically what I meant, but I tried writing it in a way that doesn't assume the reader knows what any of this means.
I think CLIENTNAME
is a bit special in this regard, because it's not even stored in the Volatile Environment
key directly, but rather in it's child key where the key name is your session ID, starting from 1. To me it seems like it is still a constant across an entire session, because every time you login or connect remotely the session ID changes and a new child key is created.
How would you express the comment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried it just now: disconnected from the session without logging out, and reconnected to the same session from a different client. In this case, "Volatile Environment" still had a subkey with the same session ID as its name, but the data of the "CLIENTNAME" value was updated. And Registry Editor was still running so it had to be the same session.
How would you express the comment?
I would not try to explain what "volatile" means.
// * call LookupAccountSidW to get the USERNAME/USERDOMAIN variables
// Windows sets these as values of the "Volatile Environment" key at each login.
// We don't expect our process to impersonate another user so we can simplify this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for testing this! I guess in hindsight this is kind of obvious, since RDP allows reconnects... I've never really thought about how that would work. I hope it properly emits a WM_SETTINGCHANGE
event, whenever this happens, so that it still works if we don't regenerate the environment every time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure let's give it a shot. I think I follow
This is a very long change for what it purports to be. Can you point out the specific part that is the fix, and what is a drive-by? Should we have a smaller, targeted version for 1.18 and 1.19? |
The only drive-by is the removal of Solution: ZFQA qbrfa'g ernyyl zragvba vg fcrpvsvpnyyl, ohg YbbxhcNppbhagFvqJ gerngf gur nppbhagAnzrFvmr naq hfreQbznvaFvmr cnenzrgref nf va-bhg barf rira vs gur gjb jpune cbvagref nera'g ahyycge. Gung vf, nsgre gur frpbaq pnyy gb YbbxhcNppbhagFvqJ, jr unq gb pnyy erfvmr() ba gur gjb jfgevat'f gb cebcreyl gevz bs genvyvat ahyyf naq fgevc_genvyvat_ahyy bayl fgevcf 1 genvyvat ahyy. |
|
||
til::env environment; | ||
auto zeroEnvMap = wil::scope_exit([&]() noexcept { | ||
environment.clear(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did we used to do this, anyway?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure! Maybe security? But that wouldn't have worked, since the creation of the many std::wstring
instances during regenerate()
would leave tons of "unsafely" freed memory locations around anyways.
strip_trailing_null(accountName); | ||
strip_trailing_null(userDomain); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
clever. this means it is no longer a clone of RegenerateUserEnvironment
, but that's OK.
data = expand_environment_strings(data.data()); | ||
} | ||
|
||
// Because Registry data may or may not be null terminated... check if we've managed | ||
// to store an extra null in the wstring by telling it to create itself from pointer and size. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this seems like a willy way to handle that!
auto expectedIt = expected.as_map().begin(); | ||
auto actualIt = actual.as_map().begin(); | ||
|
||
for (; expectedIt != expectedEnd; ++expectedIt, ++actualIt) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Careful! The original check is order-independent. This check is not. It was explicitly written this way because the test was failing.
It's just test code, it's allowed to be inefficient.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did it use an unordered_map
before? Right now it uses a map
and the iteration order of the two instances should match 1:1.
I made this change because I found the old code very confusing while testing my changes. This doesn't work:
const auto actualValue = actual.as_map()[expectedKey];
VERIFY_IS_TRUE(actual.as_map().find(expectedKey) != actual.as_map().end());
The first line ensures that the second line always succeeds. This code is also somewhat confusing to me:
VERIFY_ARE_EQUAL(expectedValue, actual.as_map()[expectedKey]);
because it doesn't properly work in case either of the two values is an empty string.
In short, my intention was to remove the use of operator[]
to increase my confidence that I didn't break anything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AH. Thanks.
eb871bf fails to properly handle REG_SZ strings, which are documented as being null-terminated _and_ length restricted. `wcsnlen` is the perfect fit for handling this situation as it returns the position of the first \0, or the given length parameter. As a drive by improvement, this also drops some redundant code: * `to_environment_strings_w` which is the same as `to_string` * Retrieving `USERNAME`/`USERDOMAIN` via `LookupAccountSidW` and `COMPUTERNAME` via `GetComputerNameW` is not necessary as the variables are "volatile" and I believe there's generally no expectation that they change unless you log in again. Closes #16051 ## Validation Steps Performed * Run this in PowerShell to insert a env value with \0: ```pwsh $hklm = [Microsoft.Win32.RegistryKey]::OpenBaseKey( [Microsoft.Win32.RegistryHive]::LocalMachine, 0 ) $key = $hklm.OpenSubKey( 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', $true ) $key.SetValue('test', "foo`0bar") ``` * All `EnvTests` still pass ✅ * (Don't forget to remove the above value again!) (cherry picked from commit 64b5b28) Service-Card-Id: 90879164 Service-Version: 1.19
eb871bf fails to properly handle REG_SZ strings, which are documented as being null-terminated _and_ length restricted. `wcsnlen` is the perfect fit for handling this situation as it returns the position of the first \0, or the given length parameter. As a drive by improvement, this also drops some redundant code: * `to_environment_strings_w` which is the same as `to_string` * Retrieving `USERNAME`/`USERDOMAIN` via `LookupAccountSidW` and `COMPUTERNAME` via `GetComputerNameW` is not necessary as the variables are "volatile" and I believe there's generally no expectation that they change unless you log in again. Closes #16051 ## Validation Steps Performed * Run this in PowerShell to insert a env value with \0: ```pwsh $hklm = [Microsoft.Win32.RegistryKey]::OpenBaseKey( [Microsoft.Win32.RegistryHive]::LocalMachine, 0 ) $key = $hklm.OpenSubKey( 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', $true ) $key.SetValue('test', "foo`0bar") ``` * All `EnvTests` still pass ✅ * (Don't forget to remove the above value again!) (cherry picked from commit 64b5b28) Service-Card-Id: 90879163 Service-Version: 1.18
eb871bf fails to properly handle REG_SZ strings, which are documented as being null-terminated _and_ length restricted. `wcsnlen` is the perfect fit for handling this situation as it returns the position of the first \0, or the given length parameter. As a drive by improvement, this also drops some redundant code: * `to_environment_strings_w` which is the same as `to_string` * Retrieving `USERNAME`/`USERDOMAIN` via `LookupAccountSidW` and `COMPUTERNAME` via `GetComputerNameW` is not necessary as the variables are "volatile" and I believe there's generally no expectation that they change unless you log in again. Closes #16051 ## Validation Steps Performed * Run this in PowerShell to insert a env value with \0: ```pwsh $hklm = [Microsoft.Win32.RegistryKey]::OpenBaseKey( [Microsoft.Win32.RegistryHive]::LocalMachine, 0 ) $key = $hklm.OpenSubKey( 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', $true ) $key.SetValue('test', "foo`0bar") ``` * All `EnvTests` still pass ✅ * (Don't forget to remove the above value again!) (cherry picked from commit 64b5b28) Service-Card-Id: 90879163 Service-Version: 1.18
eb871bf fails to properly handle REG_SZ strings, which are documented as
being null-terminated and length restricted.
wcsnlen
is the perfect fit for handling this situation as it returnsthe position of the first \0, or the given length parameter.
As a drive by improvement, this also drops some redundant code:
to_environment_strings_w
which is the same asto_string
USERNAME
/USERDOMAIN
viaLookupAccountSidW
andCOMPUTERNAME
viaGetComputerNameW
is not necessary as thevariables are "volatile" and I believe there's generally no
expectation that they change unless you log in again.
Closes #16051
Validation Steps Performed
EnvTests
still pass ✅