Skip to content

Fix fstatat#1351

Merged
sporksmith merged 10 commits intoshadow:devfrom
sporksmith:b1332
May 13, 2021
Merged

Fix fstatat#1351
sporksmith merged 10 commits intoshadow:devfrom
sporksmith:b1332

Conversation

@sporksmith
Copy link
Copy Markdown
Contributor

@sporksmith sporksmith commented May 12, 2021

  • TypedPluginPtr: make slice method generic over RangeBounds
  • MemoryManager.read_ptr: let caller slice instead of taking offset
  • MemoryManager: generalize read_ptr -> readv_ptr
  • Split MemoryReader.copy to read_some and read_exact
  • MemoryReader: split as_ref into ref_some and ref_exact
  • Reimplement process_getReadableString using new Reader API
  • Add process_readString
  • fileat syscalls: fix memory access bugs
  • Add test for fstatat

Fixes #1332

@codecov
Copy link
Copy Markdown

codecov bot commented May 12, 2021

Codecov Report

Merging #1351 (e3c27a7) into dev (b277326) will increase coverage by 0.09%.
The diff coverage is 35.55%.

Impacted file tree graph

@@            Coverage Diff             @@
##              dev    #1351      +/-   ##
==========================================
+ Coverage   56.43%   56.52%   +0.09%     
==========================================
  Files         141      141              
  Lines       20084    20098      +14     
  Branches     4977     4983       +6     
==========================================
+ Hits        11335    11361      +26     
+ Misses       5868     5839      -29     
- Partials     2881     2898      +17     
Flag Coverage Δ
tests 56.52% <35.55%> (+0.09%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
src/main/host/syscall/fileat.c 15.87% <10.00%> (+6.49%) ⬆️
src/test/file/test_file.c 51.83% <41.66%> (-0.60%) ⬇️
src/main/host/process.c 70.02% <69.23%> (-0.05%) ⬇️
src/support/logger/rust_bindings/src/lib.rs 40.26% <0.00%> (-0.30%) ⬇️
src/main/host/descriptor/file.c 34.07% <0.00%> (+0.66%) ⬆️
src/main/host/syscall_handler.c 53.16% <0.00%> (+0.84%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update b277326...e3c27a7. Read the comment docs.

@sporksmith sporksmith changed the title B1332 Fix fstatat May 12, 2021
@sporksmith sporksmith marked this pull request as ready for review May 12, 2021 15:43
@sporksmith sporksmith requested a review from stevenengler May 12, 2021 15:43
@robgjansen robgjansen added Component: Main Composing the core Shadow executable Type: Bug Error or flaw producing unexpected results labels May 12, 2021
pub fn slice<R: std::ops::RangeBounds<usize>>(&self, range: R) -> TypedPluginPtr<T> {
use std::ops::Bound;
let excluded_end = match range.end_bound() {
Bound::Included(e) => e + 1,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This can technically overflow, but shouldn't in practice. Maybe use checked_add().unwrap() here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

IIUC overflow will be caught in Debug builds; given that, I don't think it's worth wrapping every arithmetic operation in checks. rust-lang/rfcs#560

let excluded_end = match range.end_bound() {
Bound::Included(e) => e + 1,
Bound::Excluded(e) => *e,
Bound::Unbounded => self.count,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should this be self.count + 1 so that it doesn't exclude the last byte?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I don't think so - we're getting the excluded end-bound. e.g. buf[..buf.len()] is all of buf.

Comment on lines +1700 to +1703
// Low level helper for reading directly from `srcs` to `dsts`.
// Returns the number of bytes read. Panics if the
// MemoryManager's process isn't currently active.
fn readv_iovecs<T: Pod + Debug>(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This generic T isn't used anywhere.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done (I'm surprised Rust didn't complain)

.memory_manager
.readv_ptrs(&mut [buf], &[self.ptr.slice(offset..)])?;
if nread != buf.len() {
Err(Errno::EFAULT)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A debug log here might be useful if we ever need to debug this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done (in helpers)

.memory_manager
.readv_ptrs(&mut [&mut vec], &[self.ptr])?;
if nread != vec.len() {
return Err(Errno::EFAULT);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A debug log here might be useful if we ever need to debug this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done (now in memory_manager.read_exact)

Comment on lines +74 to 84
/// Get a reference to the readable prefix of the region.
pub fn ref_some(&self) -> Result<&[T], Errno> {
(&*self.reader).ref_some()
}

/// Get a reference to the whole region, or return an error.
pub fn ref_exact(&self) -> Result<&[T], Errno> {
(&*self.reader).ref_exact()
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can the (&*self.reader) be just self.reader?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment on lines +365 to +382
// Split at page boundaries to allow partial reads.
let mut slices = Vec::with_capacity((buf.len() + page_size() - 1) / page_size());
let mut toread = buf.len();
let mut buf = buf;
while toread > 0 {
let mut this_toread = toread % page_size();
if this_toread == 0 {
this_toread = page_size();
}
let (prefix, suffix) = buf.split_at_mut(this_toread);
buf = suffix;
slices.push(prefix);
toread -= this_toread;
}
let rv = self
.memory_manager
.readv_ptrs(&mut slices, &[self.ptr.slice(offset..)]);
rv
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  1. This seems like it's mixing counts (buf.len()) with bytes (page_size()). Should this be using buf.len() * sizeof(T)?
  2. This seems to be reading in chunks of page_size(), but the chunks don't seem to be aligned at page boundaries if self.ptr itself isn't aligned to a page boundary.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This seems like it's mixing counts (buf.len()) with bytes (page_size()). Should this be using buf.len() * sizeof(T)?

Oops; you're right. I'd originally implemented only for u8 intead of for T, but missed the corresponding changes here. (I couldn't figure out a way to define methods in a trait only when T=u8)

I changed the MemoryManager helpers to only take u8 and put the conversions here.

This seems to be reading in chunks of page_size(), but the chunks don't seem to be aligned at page boundaries if self.ptr itself isn't aligned to a page boundary.

... wow yeah I totally botched that. Thanks for catching it!

Comment on lines +346 to 374
fn read_some(&self, offset: usize, buf: &mut [T]) -> Result<usize, Errno> {
self.memory_manager
.readv_ptrs(&mut [buf], &[self.ptr.slice(offset..)])
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In order to stay consistent with the other functions, I think read_some() should return the number of T items, not the number of bytes returned by readv_ptrs(). So it should return self.memory_manager.readv_ptrs() / size_of(T).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment on lines +2020 to +2066
let reader = &*reader;
let dst = std::slice::from_raw_parts_mut(str as *mut u8, strlen);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think it would be good to assert that str here isn't null.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I started to add debug_asserts, but I think we can depend on Rust to do it for us. e.g. https://doc.rust-lang.org/src/core/slice/raw.rs.html#130

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think debug asserts in the standard library run, for example this code doesn't panic: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=9fbb2471cca367c2624d2a04767e37e8

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Oh wow, that's surprising. I'll go ahead and add it then. I see I also missed a few other comments somehow; will make another pass

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

@sporksmith
Copy link
Copy Markdown
Contributor Author

I realized that currently if the caller calls CopyingMemoryReader::ref_exact, and it fails, later calls to CopyingMemoryReader::ref_some will also fail. I also realized that we already have a similar issue with CopyingMemoryWriter::as_mut and CopyingMemoryWriter::as_mut_uninit; the first call "wins".

I think what I'd like to do is move the public methods returning references into MemoryManager, so that the exact type of reference is determined at the time of creation. Probably better to save that for another PR, though.

Thank you for your patience and diligence reviewing all these iterations!

@sporksmith
Copy link
Copy Markdown
Contributor Author

I realized that currently if the caller calls CopyingMemoryReader::ref_exact, and it fails, later calls to CopyingMemoryReader::ref_some will also fail

Oh no, that's right I did fix that. But the way I fixed it is that we always go through the more complex read_some path when copying the data. If we move the ref-returning methods into the MemoryManager though, ref_exact can go through the simpler path (which may be marginally faster). mut vs mut_uninit is the bigger issue.

@sporksmith sporksmith requested a review from stevenengler May 12, 2021 23:48
@stevenengler
Copy link
Copy Markdown
Contributor

I realized that currently if the caller calls CopyingMemoryReader::ref_exact, and it fails, later calls to CopyingMemoryReader::ref_some will also fail

Oh no, that's right I did fix that. But the way I fixed it is that we always go through the more complex read_some path when copying the data. If we move the ref-returning methods into the MemoryManager though, ref_exact can go through the simpler path (which may be marginally faster). mut vs mut_uninit is the bigger issue.

I don't think I follow these comments, but I'll wait to see what the next change looks like :)

sporksmith added 10 commits May 13, 2021 10:07
* Copy file names instead of holding a reference when we also need to
  get a mutable reference. Holding an immutable + mutable reference
  simultaneously would break Rust's soundness contract, and currently
  results in a run-time panic in the C wrappers.
* Fix missing NULL checks for return values of process_getReadablePtr
  and process_getWriteablePtr.
* Remove unnecessary NULL checks for user pointers. The MemoryManager
  functions will themselves return NULL if the user pointer isn't
  accessible.
@sporksmith sporksmith enabled auto-merge May 13, 2021 15:08
@sporksmith sporksmith merged commit bc735d3 into shadow:dev May 13, 2021
@sporksmith sporksmith deleted the b1332 branch May 13, 2021 15:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Component: Main Composing the core Shadow executable Type: Bug Error or flaw producing unexpected results

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants