Skip to content
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

Fix #1 #2

Merged
merged 5 commits into from
Aug 19, 2024
Merged

Fix #1 #2

merged 5 commits into from
Aug 19, 2024

Conversation

oddcoder
Copy link
Owner

@oddcoder oddcoder commented Aug 19, 2024

This is patch series that fixes the unsoundness bug at #1

attrs
.iter()
.filter(|attr| !attr.path().is_ident("repr"))
.count()

Choose a reason for hiding this comment

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

Nit: Probably any() is a better use case for this: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.any

attrs.iter().any(|attr| !attr.path().is_ident("repr"))

Side-Note: I actually didn't know that the derive macro can't see the derive attributes. TIL.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Side-Note: I actually didn't know that the derive macro can't see the derive attributes. TIL.

Apparently i is a bit tricky, they cannot see derive attribute, and (I think) they cannot see attributed defined before them if they did not persist after the attribute is being called.

if contains_attribute_macros(&tagged_enum.attrs) {
return Err(Error::new(
tagged_enum.ident.span(),
"Discriminant is not compatiable with any top \
Copy link

@trent-reed trent-reed Aug 19, 2024

Choose a reason for hiding this comment

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

Nit: I'm still not totally sure how I feel about these \ continuations here. It seems like that will create a string like this: "Discriminant is not compatiable with any top (lots of spaces here) level #[attr] except #[repr(_)]."

Maybe the compiler error is reformatted so the end user can't see this, so probably not a huge deal, but concat! is how I thought you were supposed to do string literals across multiple lines: https://doc.rust-lang.org/std/macro.concat.html

Copy link
Owner Author

Choose a reason for hiding this comment

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

They do the same, I am used to the \ notation because it is similar to C, but we can standardize using concat!

@@ -107,3 +124,26 @@ pub fn derive_discriminant(item: TokenStream) -> TokenStream {
Ok(s) => s,
}
}

#[cfg(feature = "test-utils")]

Choose a reason for hiding this comment

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

Optional/Nit: Slight preference to put this in a separate module and just #[cfg(feature = "test-utils")] the module instead of doing it per testonly attribute.

I think it'd read a lot clearer in tests too:

use safe_discriminant::Discriminant;
use safe_discriminant_derive::testonly::repr;

#[derive(Discriminant)]
#[repr(u8)]
pub enum Foo {
    A = 0,
    B = 1,
}

fn main() {}

Copy link
Owner Author

Choose a reason for hiding this comment

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

That is unfortunately no possible, all proc macros must be defined in the top level lib.rs.

// this test makes sure that remove repr actually removes repr

use safe_discriminant_derive::remove_repr;

Choose a reason for hiding this comment

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

Optional/Nit: Some extra spacing and strange formatting in the file.

Copy link
Owner Author

Choose a reason for hiding this comment

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

righ, I forgot that cargo format doesn't not format those files, will format with rustfmt

@@ -80,13 +80,30 @@ fn validate_all_variants(variants: impl Iterator<Item = Variant>) -> Result<()>
.unwrap_or(Ok(()))
}

/// Returns true if there is any #[x] where x is not `repr`
/// returns false otherwise.
fn contains_attribute_macros(attrs: &[Attribute]) -> bool {

Choose a reason for hiding this comment

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

I think if we want to get super technical, what we really want to say is that there are no attributes past the derive macro that is implementing the Discriminant trait.

I actually think that's what this code accomplishes, since I think that the other attribute macros will be consumed in-order, so I don't think you can see the attributes after they've taken effect. But the chat logs that you pointed to seem to suggest otherwise and that you can see other attribute macros after they've been expanded. Am I reading this right?

Anyways, consider this an optional request, but it might be nice to try to create an attribute macro that isn't malicious and does nothing and see if it gets compiled out so that we don't see it as long as it's positioned before the derive attribute. And if so, we can test a non-malicious attribute macro to see that it produces what we expect (an error if placed after the derive attribute, success if placed before).

I'm not totally sure what would happen, but maybe worth playing with.

Copy link

@trent-reed trent-reed left a comment

Choose a reason for hiding this comment

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

Very great catch to the reporter - I'm not used to thinking about attribute macros, so that's definitely a blind spot for me. Sorry about missing it.

Aside from some nits and possible other tests, it looks good to me. Thanks so much for the diligence here!

@oddcoder
Copy link
Owner Author

oddcoder commented Aug 19, 2024

I think I found another soundness bug \o/, It follows the same theme!

#[derive(Discriminant)]
#[repr(u8)]
pub enum Foo {
#[remove_disc]
    A = 0,
    B = 1,
}

if #[remove_disc] replaces A = 0 with A, that would break our second safety condition. Will have to prototype it first.

This macro removes `#[repr(_)]` from an enum, this is useful for testing
the bug described in #1. `#[remove_repr]` is only available behind the
feature flag test-utils.

Signed-off-by: Ahmed Abdelraoof <[email protected]>
This patch adds a fake `repr` and add tests to try to confuse the
compiler into thinking that it is the real repr. If possible, that would
cause problems with safe-discriminant. That is because we assume that
there is always a repr attributed.

Signed-off-by: Ahmed Abdelraoof <[email protected]>
This patch checks if there is any top level attribute macro expansion,
and if there it, we report it as an error.

fixes: #1

Signed-off-by: Ahmed Abdelraoof <[email protected]>
Signed-off-by: Ahmed Abdelraoof <[email protected]>
@oddcoder
Copy link
Owner Author

Very great catch to the reporter - I'm not used to thinking about attribute macros, so that's definitely a blind spot for me. Sorry about missing it.

No need to be sorry, We learned a new trick for rust unsafe, something tells me many library that does AST level checks in macros will have the same kind of bugs, maybe including zero-copy, but I will check later.

I addressed all you comments, I will merge this PR, to keep it short, and start a new fix for the second soundness bug I discovered, I guess the lesson (at least for me) learnt here is that there is no such thing as too simple or too obvious unsafe {..} code.

@oddcoder oddcoder merged commit 4b23fc3 into main Aug 19, 2024
4 checks passed
@oddcoder oddcoder deleted the soundness_bug branch August 19, 2024 20:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants