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

New lint: unnecessary use of std::ptr::read_unaligned() with slices #4891

Open
Shnatsel opened this issue Dec 8, 2019 · 9 comments
Open
Labels
A-lint Area: New lints E-medium Call for participation: Medium difficulty level problem and requires some initial experience. L-style Lint: Belongs in the style lint group T-middle Type: Probably requires verifiying types

Comments

@Shnatsel
Copy link
Member

Shnatsel commented Dec 8, 2019

During safety-dance audit I've encountered the following unsafe code pattern that can be converted to safe code:

fn read_u32(bytes: &[u8]) -> u32 {
    assert!(bytes.len() >= 4); // bounds check
    unsafe { ptr::read_unaligned(*bytes as *const u32)}
}

This is common in binary format decoders that need to take a chunk of byte stream and interpret it as a value. The exact target value may vary - it can be any primitive numerical type.

Ever since TryInto got stabilized this can be rewritten in safe code with identical performance, although the safe solution is not really obvious:

fn read_u32(bytes: &[u8]) -> u32 {
    // try_into() cannot fail because slice len is always 4
    let bytes_to_convert: [u8; 4] = bytes[..4].try_into().unwrap();
    u32::from_ne_bytes(bytes_to_convert)
}

This may look like it does a lot of more and would be slower, but rustc produces identical code for both versions.

If converting to f32 or f64, the last line would instead be the following: f32::from_bits(u32::from_ne_bytes(bytes_to_convert))

See from_bits() documentation for more info on converting bytes to floats.

@Shnatsel
Copy link
Member Author

Shnatsel commented Dec 8, 2019

Also, if this is done in a loop, using slice::chunks_exact() to obtain correctly sized slices is a safe alternative that also avoids bounds checks.

@Shnatsel Shnatsel changed the title New lint: unnecessary use of std::prt::read_unaligned() with slices New lint: unnecessary use of std::ptr::read_unaligned() with slices Dec 8, 2019
@Shnatsel
Copy link
Member Author

Shnatsel commented Dec 8, 2019

Real-world example of such pattern and its refactoring into safe code: Frommi/miniz_oxide@7bcf211

@Shnatsel
Copy link
Member Author

Shnatsel commented Dec 8, 2019

@Lokathor
Copy link

Lokathor commented Dec 8, 2019

in debug and also release? or just in release?

@Shnatsel
Copy link
Member Author

Shnatsel commented Dec 8, 2019

I've only tried release mode.

@Lokathor
Copy link

Lokathor commented Dec 8, 2019

Debug performance is important and I deeply doubt you can beat read_unaligned in debug mode.

@alex
Copy link
Member

alex commented Dec 8, 2019

Maybe? https://rust.godbolt.org/z/EdGzzr both implementations are utterly insane due to how many calls there are. Even len() is a call!

@Shnatsel
Copy link
Member Author

Shnatsel commented Dec 8, 2019

To put this in context, the original motivating example in miniz_oxide was UB in two ways at once: not only it had a buffer overflow, but also violated pointer provenance rules. This did not make all users of zip and reqwest crates exploitable in production by sheer luck - and we still aren't 100% sure they aren't exploitable.

Even if read_unaligned() has debug mode benefits, it is not worth the risk due to how tricky and dangerous this pattern is.

See also rust-lang/rust#62408 for more info on the potential tradeoff between safety and debug mode performance

@Lokathor
Copy link

Lokathor commented Dec 8, 2019

Neither of those problems are related to read unaligned itself.

I'm surprised that you don't want a lint for mixing Index with pointer creation, because that's an actual problem in this code you replaced.

@camsteffen camsteffen added L-style Lint: Belongs in the style lint group E-medium Call for participation: Medium difficulty level problem and requires some initial experience. A-lint Area: New lints T-middle Type: Probably requires verifiying types labels Jan 26, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-lint Area: New lints E-medium Call for participation: Medium difficulty level problem and requires some initial experience. L-style Lint: Belongs in the style lint group T-middle Type: Probably requires verifiying types
Projects
None yet
Development

No branches or pull requests

4 participants