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 lookahead buffer size reported to littlefs2-sys #24

Merged
merged 3 commits into from
Feb 7, 2023

Conversation

robin-nitrokey
Copy link
Member

Previously, we reported the lookahead buffer size in bytes but littlefs2-sys expects the lookahead buffer size as a multiple of 8 bytes. This could lead to a buffer overflow causing filesystem corruption. This patch fixes the reported lookahead buffer size.

Note that Storage::LOOKAHEAD_WORDS_SIZE allows users to set invalid values (as it is measured in 4 bytes, not in 8 bytes). Invalid values that were previously accepted because of the wrong buffer size calculation can now be rejected by littlefs2-sys.

This is a combination of two previous patches:
#19
Nitrokey#1

Fixes: #16


I’m wondering whether we should treat this as a breaking change or not. On the one hand, this is a bug fix. On the other hand, it may cause code that previously worked (because the size calculation was wrong) to fail. If we consider this to be a breaking change, we should also change Storage::LOOKAHEAD_BUFFER_SIZE so that it uses multiples of 8 and it is no longer possible to set invalid values.

Also, it would be good to have a test case that demonstrates the bug.

cc @arturkow2000

@daringer
Copy link
Member

lgtm, this is something we should verify on actual hardware, just to be sure and as finding this as a source for issues at a later point might be painful

@robin-nitrokey
Copy link
Member Author

Note that this is effectively the same patch that we are already using in nitrokey-3-firmware.

@daringer
Copy link
Member

daringer commented Jan 27, 2023

just the usual inner panic on my side once something touches littlefs2, but you're right, this should be fine.

@nickray
Copy link
Member

nickray commented Jan 30, 2023

I'd be +1 on making this a breaking change, and adding whatever other improvements you can think of (e.g. your "should also change").

@robin-nitrokey
Copy link
Member Author

Updated:

  • Rebased onto 7b66857.
  • Replaced LOOKAHEADWORDS_SIZE (measured in 4 bytes) with LOOKAHEAD_SIZE (measured in 8 bytes) so that all possible values are valid.
  • Bumped Rust version back to 1.67.0.

Not tested on hardware yet, also no unit test for the issue yet. But at least the 1.67.0 segfault fix shows us that we are doing something right. :-)

I’d like to merge and release this next week as this a rather severe issue.

@trussed-dev/nitrokey Please review, test and comment.
@daringer Can you test this on the NK3AM with your load test script?
@arturkow2000 @lexxvir @svenstaro If you’re still working with littlefs2, would you mind testing this change in your projects?

src/fs.rs Outdated
Comment on lines 90 to 92
// lookahead words size (measured in 4 bytes) must be a multiple of 2 so that the actual
// lookahead size is a multiple of 8 bytes
debug_assert!(lookahead_size % 2 == 0);
Copy link
Member Author

Choose a reason for hiding this comment

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

Forgot about this one, has to be removed as we now use multiples of 8 bytes.

@nickray
Copy link
Member

nickray commented Feb 5, 2023

I'm wondering if we should have all the sizes (at least external facing) in bytes, and use compile time checks to enforce multiples, and compile time additions for the "plus one" stuff -- if possible -- as it would make for a cleaner API.

@robin-nitrokey
Copy link
Member Author

I don’t think this is possible at the moment because of the limitations with the const generics in stable. We cannot use generic parameters in const operations, so we still need generic-array which cannot do arithmetics on the array size AFAIK. And we cannot use generic parameters in const assertions.

I thought about adding an optional verification method that could be used to validate a Storage implementation, but it is nothing we can enforce. So I’d prefer to stick with the current approach for compile-time checks.

@sosthene-nitrokey
Copy link
Contributor

And we cannot use generic parameters in const assertions.

This can be done by having an associated constant for the const assertion: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=40f243447104f266f22aa71b3f18c359

@robin-nitrokey
Copy link
Member Author

robin-nitrokey commented Feb 6, 2023

In this case, it is about accessing an associated constant of a generic parameter. Moving all associated constant into parameters is unrealistic.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=4e53f8caa18adb02be25667bbf27d3bc

littlefs2/src/driver.rs

Lines 25 to 103 in 7b66857

pub trait Storage {
// /// Error type for user-provided read/write/erase methods
// type Error = usize;
/// Minimum size of block read in bytes. Not in superblock
const READ_SIZE: usize;
/// Minimum size of block write in bytes. Not in superblock
const WRITE_SIZE: usize;
/// Size of an erasable block in bytes, as unsigned typenum.
/// Must be a multiple of both `READ_SIZE` and `WRITE_SIZE`.
/// At least 128 (https://git.io/JeHp9). Stored in superblock.
const BLOCK_SIZE: usize;
/// Number of erasable blocks.
/// Hence storage capacity is `BLOCK_COUNT * BLOCK_SIZE`
const BLOCK_COUNT: usize;
/// Suggested values are 100-1000, higher is more performant but
/// less wear-leveled. Default of -1 disables wear-leveling.
/// Value zero is invalid, must be positive or -1.
const BLOCK_CYCLES: isize = -1;
/// littlefs uses a read cache, a write cache, and one cache per per file.
/// Must be a multiple of `READ_SIZE` and `WRITE_SIZE`.
/// Must be a factor of `BLOCK_SIZE`.
type CACHE_SIZE: ArrayLength<u8>;
/// littlefs itself has a `LOOKAHEAD_SIZE`, which must be a multiple of 8,
/// as it stores data in a bitmap. It also asks for 4-byte aligned buffers.
/// Hence, we further restrict `LOOKAHEAD_SIZE` to be a multiple of 32.
/// Our LOOKAHEADWORDS_SIZE is this multiple.
type LOOKAHEADWORDS_SIZE: ArrayLength<u32>;
// type LOOKAHEAD_SIZE: ArrayLength<u8>;
///// Maximum length of a filename plus one. Stored in superblock.
///// Should default to 255+1, but associated type defaults don't exist currently.
///// At most 1_022+1.
/////
///// TODO: We can't actually change this - need to pass on as compile flag
///// to the C backend.
//type FILENAME_MAX_PLUS_ONE: ArrayLength<u8>;
// /// Maximum length of a path plus one. Necessary to convert Rust string slices
// /// to C strings, which requires an allocation for the terminating
// /// zero-byte. If in doubt, set to `FILENAME_MAX_PLUS_ONE`.
// /// Must be larger than `FILENAME_MAX_PLUS_ONE`.
// type PATH_MAX_PLUS_ONE: ArrayLength<u8>;
///// Maximum size of file. Stored in superblock.
///// Defaults to 2_147_483_647 (or u31, to avoid sign issues in the C code).
///// At most 2_147_483_647.
/////
///// TODO: We can't actually change this - need to pass on as compile flag
///// to the C backend.
//const FILEBYTES_MAX: usize = ll::LFS_FILE_MAX as _;
///// Maximum size of custom attributes.
///// Should default to 1_022, but associated type defaults don't exists currently.
///// At most 1_022.
/////
///// TODO: We can't actually change this - need to pass on as compile flag
///// to the C backend.
//type ATTRBYTES_MAX: ArrayLength<u8>;
/// Read data from the storage device.
/// Guaranteed to be called only with bufs of length a multiple of READ_SIZE.
fn read(&mut self, off: usize, buf: &mut [u8]) -> Result<usize>;
/// Write data to the storage device.
/// Guaranteed to be called only with bufs of length a multiple of WRITE_SIZE.
fn write(&mut self, off: usize, data: &[u8]) -> Result<usize>;
/// Erase data from the storage device.
/// Guaranteed to be called only with bufs of length a multiple of BLOCK_SIZE.
fn erase(&mut self, off: usize, len: usize) -> Result<usize>;
// /// Synchronize writes to the storage device.
// fn sync(&mut self) -> Result<usize>;
}

@robin-nitrokey
Copy link
Member Author

I think the tracking issue for this would be: rust-lang/rust#76560

@daringer
Copy link
Member

daringer commented Feb 6, 2023

ok tested on nrf extensively and not on lpc55, but all adaptations needed are available as PRs:

nk3xn & nk3am.bl compile and nk3am.bl has seen ~200 RK operations, ~15 resets, ~50 pin-sets/changes w/o any issues.

Soooo, lgtm 👯

daringer added a commit to trussed-dev/trussed that referenced this pull request Feb 6, 2023
daringer added a commit to Nitrokey/trussed that referenced this pull request Feb 6, 2023
@robin-nitrokey
Copy link
Member Author

robin-nitrokey commented Feb 6, 2023

Nice! Thanks @daringer! I’ve tested it briefly on lpc55 with the NK3CN (provisioning FIDO2, setting a PIN, registration and authentication with a resident key) and everything worked. For me this PR is ready to merge – any objections?

@daringer
Copy link
Member

daringer commented Feb 6, 2023

For me this PR is ready to merge – any objections?

nope, let's go

@nickray
Copy link
Member

nickray commented Feb 7, 2023

Before we do so, please remove rust-toolchain.toml instead of bumping it.
I have a fix but couldn't push on top of this PR.

@sosthene-nitrokey
Copy link
Contributor

sosthene-nitrokey commented Feb 7, 2023

Regarding the question on whether this is a breaking change or not, it doesn't really matter as 5d31dc5 is clearly a breaking change, so the next release will need to be a major semver anyway.

Previously, we reported the lookahead buffer size in bytes but
littlefs2-sys expects the lookahead buffer size as a multiple of 8
bytes.  This could lead to a buffer overflow causing filesystem
corruption.  This patch fixes the reported lookahead buffer size.

Note that Storage::LOOKAHEAD_WORDS_SIZE allows users to set invalid
values (as it is measured in 4 bytes, not in 8 bytes).  Invalid values
that were previously accepted because of the wrong buffer size
calculation can now be rejected by littlefs2-sys.

This is a combination of two previous patches:
	trussed-dev#19
	#1

Fixes: trussed-dev#16
This patch replaces the LOOKAHEADWORDS_SIZE in driver::Storage (measured
in 4 bytes) with LOOKAHEAD_SIZE (measure in 8 bytes).  This makes it
impossible to set illegal values.
As we fixed the lookahead buffer overflow, we no longer have to pin the
Rust version.

Fixes trussed-dev#26
Fixes trussed-dev#28
@szszszsz
Copy link

szszszsz commented Feb 7, 2023

Suggestion: test all applications next time, not only fido-auth.

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.

LittleFS corruption problem
5 participants