Skip to content

Workaround for memory unsafety in third party DLLs #143090

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

ChrisDenton
Copy link
Member

@ChrisDenton ChrisDenton commented Jun 27, 2025

Resolves #143078

Note that we can't make any guarantees if third parties intercept OS functions and don't implement them according to the documentation. However, I think it's practical to attempt mitigations when issues are encountered in the wild and the mitigation itself isn't too invasive.

@rustbot
Copy link
Collaborator

rustbot commented Jun 27, 2025

r? @tgross35

rustbot has assigned @tgross35.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Jun 27, 2025
@RalfJung
Copy link
Member

and the mitigation itself isn't too invasive.

Won't this cause extra copies and thus potentially hurt performance?

@ChrisDenton
Copy link
Member Author

Potentially, I guess. Though I'd expect filesystem APIs are almost certainly going to be much slower than a memcpy.

As I mentioned in a comment, the fastest alternative would be to just ensure the empty string is null terminated. The assumption being that, however they're impersonating system APIs, they're at least being internally consistent.

@workingjubilee
Copy link
Member

Shipping a workaround upstream for unsound software means that we are expecting all Rust programmers, not just Firefox, to pay for carrying the weight of unsound ecosystem software that they are not expected to interoperate with. Even if it's just a few bytes of code size we wouldn't need to pay otherwise, I don't think it makes sense.

@workingjubilee
Copy link
Member

You cited us as shipping workarounds before, but the ones I remember were for Microsoft software. Where, yes, we may have done whatever the owners of the platform expected us to do, no matter how inane it may have seemed, because it doesn't matter if whatever is objectively incorrect if the local monarch will hang you(r process) for defiance.

@ChrisDenton
Copy link
Member Author

The one I'm thinking of was for alignment issues with a third party sandbox. This issue was fixed but we do still have the workaround under the assumption that other software may not do alignment properly. I guess it could be removed now but it also might break something.

@workingjubilee
Copy link
Member

Ah.

Yes, open source software that we can see whether it is patched or not still seems like a much different story.

@the8472
Copy link
Member

the8472 commented Jun 27, 2025

Can we set a deadline and make it public?

@rustbot rustbot added the O-windows Operating system: Windows label Jun 27, 2025
@ChrisDenton
Copy link
Member Author

ChrisDenton commented Jun 27, 2025

Ok, I've changed it so that the impact will be minimal. The updated PR just tries to ensure null termination for our own strings and leave it up to the OS to use null termination for strings it owns (where "the OS" in this case includes people emulating OS APIs).

@workingjubilee
Copy link
Member

This seems more tolerable. I have sometimes wondered if maybe core::ffi should export a static BIG_ZERO: u128 = 0;, as pointers to it could reuse it as a sentinel.

/// It is preferable to use `from_slice_with_nul` for our own strings as a
/// mitigation for #143078.
#[derive(Copy, Clone)]
pub struct UnicodeStrRef<'a> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you mention in the docs something about this optionally being nul-terminated but that not being a guarantee? To help explain why the functions aren't unsafe.

Comment on lines 93 to 95
let mut path_str = c::UNICODE_STRING::from_ref(path);
let path_str = UnicodeStrRef::from_slice(path);
let mut object = c::OBJECT_ATTRIBUTES {
ObjectName: &mut path_str,
ObjectName: path_str.as_ptr(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I am understanding things correctly, this change only ensures nul-termination if path is empty but otherwise passes path unchanged? What is the benefit here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the non-empty case the path is always given to us by the OS via directory iteration. So that falls under assuming OS returned strings are OK to use.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it use from_slice_with_nul then? Or something like from_slice_with_nul_if_nonempty that still redirects the empty slice nul pointer but makes it clear that \0 is otherwise expected.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see what you mean. The slice itself doesn't contain the nul (if any). There may or may not be a nul one beyond the end of the slice but we don't know. The point is we just assume there is if some third party is hooking OS functions and requires strings be null-terminated but otherwise we assume nothing outside of the slice.

We can't really do better than that without being more invasive. The APIs themselves say nothing about null termination.

@tgross35
Copy link
Contributor

I'm thinking maybe we should leave #143078 open once this merges, or make a new issue for eventually stripping this out. The software that will be relying on this seems objectively incorrect, and we have no way of knowing what calls actually need this; or really noticing if the UnicodeStrRef API here winds up used incorrectly (since nul termination isn't an invariant).

I'm on board with @the8472's suggestion that we should document in that issue that we will be reverting this PR in ~6 months or so, so we don't wind up carrying a workaround forever.

@ChrisDenton
Copy link
Member Author

Based on the feedback I decided to simplify this down to a single unicode_str! macro so people don't have to think about what to use where. When creating a UNICODE_STRING just use the macro. It will null-terminate if given a literal &str. If given a &[u16] it will simply use it as-is, which is the best we can do without allocating.

I think this will make it easy for people to do the right thing by default.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
O-windows Operating system: Windows S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs Relevant to the library team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

remove_dir_all implementation on Windows is incompatible with some third-party hooks for NtOpenFile, leading to Firefox crashes
6 participants