3

CFI: Support self_cell-like recursion by maurer · Pull Request #122875 · rust-la...

 1 month ago
source link: https://github.com/rust-lang/rust/pull/122875
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Conversation

Contributor

Current transform_ty attempts to avoid cycles when normalizing #[repr(transparent)] types to their interior, but runs afoul of this pattern used in self_cell:

struct X<T> {
  x: u8,
  p: PhantomData<T>,
}

 #[repr(transparent)]
struct Y(X<Y>);

When attempting to normalize Y, it will still cycle indefinitely. By using a types-visited list, this will instead get expanded exactly one layer deep to X, and then stop, not attempting to normalize Y any further.

This PR was split off from #121962 as part of fixing the larger vtable compatibility issues.

r? @workingjubilee

rustbot

added PG-exploit-mitigations Project group: Exploit mitigations S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

labels

Mar 22, 2024

Collaborator

Some changes occurred in tests/ui/sanitizer

cc @rust-lang/project-exploit-mitigations, @rcvalle

Some changes occurred in compiler/rustc_symbol_mangling/src/typeid

cc @rust-lang/project-exploit-mitigations, @rcvalle

Comment on lines

894 to 895

let mut parents = parents.to_vec();

parents.push(ty);

so... given this is recursive, this is just gonna be unbounded memory usage, huh...?

I cannot agree we should merge this without someone answering first how much recursion it takes to kill the compiler here?

Contributor

Author

So, the size of parents only increases when it unwraps a transparent type it hasn't already visited. This means the size of parents is limited to a spine of #[repr(transparent)] types in depth.

I currently have it optimized to avoid any allocations in the common case (no transparents) by taking a &[]. If we want to make it work in a way that's optimal for maximal memory usage, I can make it take a &mut Vec<u8> instead, and .truncate() after this if statement to reset it. (That would make the total memory used by this O(n) instead of O(n^2)).

I just went to double check, and evidently Vec::new() doesn't cost an allocation if you don't ever push to it. Would you like me to switch to that?

To be clear, I may or may not decide the amount of recursion this handles is acceptable, but there's a number and I want it.

Contributor

Author

Do you mean that you want the stack depth that the compiler goes to before dying without this patch, or do you mean you want to know how deeply I can nest transparent structs before the compiler gives up? (I'm guessing they'll be similar, because it'll be based on the stack frame count, not this vector, but I just want to make sure I understand you correctly.)

how deep the struct nesting must be, yes.

If it dies because of sigsegv instead, that's reassuring!

Contributor

Author

Seems like it needs to be pretty deep before it dies. This will stop dying with one layer of alternation removed in today's nightly rustc, and will stop dying with two layers of alternation removed with the parent tracking (slightly bigger stack frame, so that's expected).

Member

Would it be possible to just generalize it similarly to what we do for recursive repr(transparent)?

Contributor

Author

Would it be possible to just generalize it similarly to what we do for recursive repr(transparent)?

That would be cool but:

  1. What type are we generalizing? PhantomData is used in this case, but I don't think it's guaranteed to be wrapped by that in particular. In the pointer generalization case, we're specifically generalizing pointer-to-anything. For this problem to go away, we need to broaden it to fixed-size-type-with-type-parameter-generalization. (The obvious case of managed pointers still bottoms out at PhantomData in all the cases I know of, so I'd have to look around a bit to see if it was possible to have another type not defined in terms of other types that has this property.)
  2. What do we generalize it to? Do we just convert any PhantomData<T> to ()?

I think the first question is the bigger one, since any fixed-size-type that isn't a pointer and has a type parameter would qualify as a potential thing to generalize, and I don't think we want to generalize all of those, do we?

Member

1. What type are we generalizing? `PhantomData` is used in this case, but I don't think it's guaranteed to be wrapped by that in particular. In the pointer generalization case,  we're specifically generalizing pointer-to-anything.

I guess it depends on how common is this pattern or how frequently do we expect it to occur.

2. What do we generalize it to? Do we just convert any `PhantomData<T>` to ()`?

Not any, but just in that case i.e., recursive repr(transparent) self_cell).

I think the first question is the bigger one, since any fixed-size-type that isn't a pointer and has a type parameter would qualify as a potential thing to generalize, and I don't think we want to generalize all of those, do we?

Do you know how common is this pattern (i.e., recursive repr(transparent) self_cell)?

Contributor

Author

1. What type are we generalizing? `PhantomData` is used in this case, but I don't think it's guaranteed to be wrapped by that in particular. In the pointer generalization case,  we're specifically generalizing pointer-to-anything.

I guess it depends on how common is this pattern or how frequently do we expect it to occur.

2. What do we generalize it to? Do we just convert any `PhantomData<T>` to ()`?

Not any, but just in that case i.e., recursive repr(transparent) self_cell).

I think the first question is the bigger one, since any fixed-size-type that isn't a pointer and has a type parameter would qualify as a potential thing to generalize, and I don't think we want to generalize all of those, do we?

Do you know how common is this pattern (i.e., recursive repr(transparent) self_cell)?

This came up because of the crate self_cell which uses it, which has 3.5 million downloads, and is transitively depended on by rustc (which is how I found this issue in the first place).

Contributor

Author

Pointer generalization doesn't solve it - I modified the test-case to avoid PhantomData and use a pointer instead, and it still happens. This is because the problem is in the self type substitution, not the fixed-size-type-parameter. (My earlier comment was an incorrect analysis.)

Member

This came up because of the crate self_cell which uses it, which has 3.5 million downloads, and is transitively depended on by rustc (which is how I found this issue in the first place).

If we're sure this is a common pattern, would it be possible to implement it by looking the type(s) ahead (instead of keeping track of the visited types)?

It would be nice to add a new test file like this one: https://github.com/rust-lang/rust/blob/master/tests/codegen/sanitizer/cfi/emit-type-metadata-id-itanium-cxx-abi-repr-transparent-types.rs for recursive repr(transparent) self_cell so we can also document the expected encoding.

Contributor

Author

maurer

commented

Mar 22, 2024

edited by rcvalle

This came up because of the crate self_cell which uses it, which has 3.5 million downloads, and is transitively depended on by rustc (which is how I found this issue in the first place).

If we're sure this is a common pattern, would it be possible to implement it by looking the type(s) ahead (instead of keeping track of the visited types)?

Wouldn't this require O(n^2) work instead of O(n) work when peeling transparent structs? As I peel a transparent struct, I'd have to walk down the remainder of the struct looking for myself to figure out if I need to do some kind of early stop. If there are N nested transparent structs, this will result in N checks, each requiring that I walk depth N looking for the type currently being transformed.

It would be nice to add a new test file like this one: https://github.com/rust-lang/rust/blob/master/tests/codegen/sanitizer/cfi/emit-type-metadata-id-itanium-cxx-abi-repr-transparent-types.rs for recursive repr(transparent) self_cell so we can also document the expected encoding.

I was intentionally avoiding this to leave this definition loose - I don't think that we want to canonicalize the encoding of a self-referential type like this. This would leave you free to adjust it to whatever you want, whether it's some kind of generalization, an early termination like this, etc. as long as it still builds and runs.

I think it makes a lot of sense to do that sort of explicit checking at types that could appear on language boundaries, because there's a defined standard. For types that are only possible internally (especially if you might want to change this to an alternate generalization approach some day, given that this currently is needed for pointer types too, not just PhantomData), I don't see a strong reason to be codifying the actual tag in a test, only that the tags match at icall sites, which this test checks.

That said, if I get feedback from the project that such a test would be beneficial, I can definitely add it to this PR.

Contributor

I think for types that only appear in Rust to Rust calls, for their encoding, they should be categorized and tested separately, because it is always okay to breaking-change the encoding, and the test should be in an "auto-blessable" form, so that contributors do not need to know about the vagaries of CFI to fix the test.

@rcvalle If you wish such a test is added, I have no objection per se, but I would prefer we open an issue for setting up the requisite infra for blessable CFI encoding tests and deferring it to a later PR, because that infra should land first.

Member

I mean, it would just to document the corner case and what it's currently being encoded to, like we do for recursive repr(transparent), but I don't have any objections if it's not added now.

Member

Wouldn't this require O(n^2) work instead of O(n) work when peeling transparent structs? As I peel a transparent struct, I'd have to walk down the remainder of the struct looking for myself to figure out if I need to do some kind of early stop. If there are N nested transparent structs, this will result in N checks, each requiring that I walk depth N looking for the type currently being transformed.

Couldn't we use ty.contains()? Would it be that bad? I'm asking because we use it already in a few places (and the Rust compiler too).

Contributor

Hmm. Well, the repr(transparent) stuff is important because it ought to remain correct in Rust-to-C FFI.... hmmm.

Member

@maurer Sorry, I inadvertently edited your comment instead of replying, but copied the original comment back. GitHub is hard 😅

Contributor

@maurer to be clear, is it possible for self_cell! types to produce things that are valid for passing to C FFI?

Contributor

Author

Upon reconsideration, I think it does make sense to have a codegen test for this. I haven't seen someone use this pattern in a way they expected to use for C FFI, but it does seem that you can represent C-compatible types like this. I'll add it to the repr-transparent test.

workingjubilee reacted with thumbs up emoji

Contributor

hokay!

Contributor

Wouldn't this require O(n^2) work instead of O(n) work when peeling transparent structs? As I peel a transparent struct, I'd have to walk down the remainder of the struct looking for myself to figure out if I need to do some kind of early stop. If there are N nested transparent structs, this will result in N checks, each requiring that I walk depth N looking for the type currently being transformed.

Couldn't we use ty.contains()? Would it be that bad? I'm asking because we use it already in a few places (and the Rust compiler too).

I may not understand what you're on about but that seems like it would be answering the wrong question since it can recurse through the type to hit the leaves.

Member

I may not understand what you're on about but that seems like it would be answering the wrong question since it can recurse through the type to hit the leaves.

You're right. That would be useful if we wanted to generalize it. I'm not sure, but I feel we could just look ahead one ty for the self_cell and (shallow?) compare with current ty?

Contributor

Perhaps! I have run out of opinions about this implementation. We should have a test, and try to avoid doing anything blatantly O(n^2) where we can avoid it, but beyond that I do not have a strong feeling on lookahead vs. caching.

Collaborator

Some changes occurred in tests/codegen/sanitizer

cc @rust-lang/project-exploit-mitigations, @rcvalle

Contributor

@maurer merge conflicts somehow?

Contributor

Author

It was a conflict with #122852 , which touched all raw pointers including some in the typeid code. I've resolved it.

Contributor

ahhh spicy.

Contributor

If a more elegant refactor is found that seems fine, but currently this looks okay.

@bors r+ rollup

Contributor

📌 Commit dec36c3 has been approved by workingjubilee

It is now in the queue for this repository.

bors

added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion.

and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties.

labels

Mar 23, 2024

bors

merged commit b9b65f8 into

rust-lang:master

Mar 24, 2024

11 checks passed

rust-timer

added a commit to rust-lang-ci/rust that referenced this pull request

Mar 24, 2024

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

workingjubilee

workingjubilee left review comments
Labels
PG-exploit-mitigations Project group: Exploit mitigations S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects

None yet

Milestone

1.79.0

Development

Successfully merging this pull request may close these issues.

None yet

5 participants

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK