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
Add readv and writev syscalls for SGX #369
Conversation
enarx-keep-sgx-shim/src/handler.rs
Outdated
let iovec = unsafe { from_raw_parts_mut(iovec as *mut Iovec, iovcnt) }; // ??? | ||
let mut bufsize: usize = 0; | ||
for i in 0..iovcnt { | ||
bufsize += iovec[i].size; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let iovec = unsafe { from_raw_parts_mut(iovec as *mut Iovec, iovcnt) }; // ??? | |
let mut bufsize: usize = 0; | |
for i in 0..iovcnt { | |
bufsize += iovec[i].size; | |
} | |
let bufsize = trusted.iter().fold(0, |a, e| a + e.size); |
enarx-keep-sgx-shim/src/handler.rs
Outdated
} | ||
|
||
// Allocate some unencrypted memory. | ||
let size = bufsize + size_of::<Iovec>() * iovcnt; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let size = bufsize + size_of::<Iovec>() * iovcnt; | |
let size = bufsize + size_of::<Iovec>() * trusted.len(); |
enarx-keep-sgx-shim/src/handler.rs
Outdated
}; | ||
|
||
// Cast beginning of allocated memory as iovec slice of length iovcnt. | ||
let map = unsafe { from_raw_parts_mut(map as *mut Iovec, iovcnt) }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let map = unsafe { from_raw_parts_mut(map as *mut Iovec, iovcnt) }; | |
let untrusted = unsafe { from_raw_parts_mut(map as *mut Iovec, iovcnt) }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The general strategy with my naming here is to always communicate to future readers whether the types are trusted or not. We want to avoid accidents...
enarx-keep-sgx-shim/src/handler.rs
Outdated
let map = unsafe { from_raw_parts_mut(map as *mut Iovec, iovcnt) }; | ||
|
||
// Set pointers in unencrypted iovec slice to use the rest of the allocated memory. | ||
let mut chunk = map.as_ptr() as usize + size_of::<Iovec>() * iovcnt; // ??? May be unsafe or illegal as per from_raw_parts_mut rules |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand the question here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking of whether I'm allowed to do map.as_ptr()
due to: "The memory referenced by the returned slice must not be accessed through any other pointer (not derived from the return value) for the duration of lifetime 'a. Both read and write accesses are forbidden." from https://doc.rust-lang.org/core/slice/fn.from_raw_parts_mut.html. But since the pointer is derived from the original 'map', maybe it's okay.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lkatalin What it means in this case is don't access the memory in this region. You can still use the base pointer to calculate a new pointer to memory after the region without harm.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@npmccallum Ah okay, thanks as usual for the insights.
enarx-keep-sgx-shim/src/handler.rs
Outdated
|
||
// Set pointers in unencrypted iovec slice to use the rest of the allocated memory. | ||
let mut chunk = map.as_ptr() as usize + size_of::<Iovec>() * iovcnt; // ??? May be unsafe or illegal as per from_raw_parts_mut rules | ||
for i in 0..iovcnt { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for i in 0..iovcnt { | |
for (t, u) in trusted.iter().zip(untrusted.iter()) { |
enarx-keep-sgx-shim/src/handler.rs
Outdated
map[i].base = chunk as *mut u8; | ||
chunk = chunk + iovec[i].size; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
map[i].base = chunk as *mut u8; | |
chunk = chunk + iovec[i].size; | |
u.base = chunk; | |
u.size = t.size; | |
chunk = chunk.add(t.size); |
enarx-keep-sgx-shim/src/handler.rs
Outdated
0, | ||
0, | ||
); // ??? | ||
self.ufree(map.as_ptr() as *mut u8, size as u64); // ??? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't free this until after you are done copying it...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean after copy_nonoverlapping
, right? I was confused about this because in read()
the ufree
is called immediately after the syscall, which I didn't understand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lkatalin You probably don't understand it because it probably doesn't work. I was a bad boy and didn't test read()
obviously... :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough!
enarx-keep-sgx-shim/src/handler.rs
Outdated
for i in 0..iovcnt { | ||
core::ptr::copy_nonoverlapping( | ||
map[i].base as *mut u8, | ||
iovec[i].base as *mut u8, | ||
ret as _, | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Iterate over the slice rather than using an index. However, keep in mind that we handed the whole array of iovecs to the untrusted host. That means that we need to validate that the values haven't been manipulated. For example, the host might replace iovec[0].base
with a pointer to enclave memory in order to trick us into copying encrypted memory somewhere else. So, like we did when initializing the array, we need to iterate over both slices. Validate that the untrusted iovec hasn't been modified (and trigger attacked()
if it has; that is suspicious behavior). Then do the copy.
Alternatively, we could take advantage of the contiguousness of the map
buffer to bypass the untrusted
array altogether. This would probably be fewer instructions. However, we'd bypass an opportunity to detect malicious behavior. So I'd prefer to be slightly slower and more security conscious.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For example, the host might replace iovec[0].base with a pointer to enclave memory ... Validate that the untrusted iovec hasn't been modified
@npmccallum For now, I'm assuming this validation means to check that none of the base
pointers refer to something inside enclave memory. If there are other checks that should be done here, please let me know. I'll also continue to think on it.
I'm fine with doing the slower and more security-conscious version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Testing that the base pointers (and base+size pointers) aren't inside the enclave is a weaker version of the testing I had in mind. I wanted to recreate the exact expected base
and size
that it should be and validate that the results haven't been tampered with in any way.
enarx-keep-sgx-shim/src/handler.rs
Outdated
let iovec = self.aex.gpr.rsi as *mut u8; | ||
let iovcnt = self.aex.gpr.rdx as usize; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let iovec = self.aex.gpr.rsi as *mut u8; | |
let iovcnt = self.aex.gpr.rdx as usize; | |
let trusted = unsafe { | |
from_raw_parts_mut( | |
self.aex.gpr.rsi as *mut Iovec, | |
self.aex.gpr.rdx as usize | |
); | |
}; |
@lkatalin Also note that |
587f363
to
b3b38ff
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not at all a fan of casting a pointer through an integer to get rid of the alignment warning. All that does it hide the problem.
I think a better strategy here is to convert the map
to a byte slice immediately. Then use split_at_mut to divide the slice into the iovec prefix and the buffer suffix. Then use align_to_mut to convert the prefix from a byte slice into an iovec slice.
@npmccallum Good point; I've tried to do it better here without casting through an integer. |
enarx-keep-sgx-shim/src/handler.rs
Outdated
let (pre, untrusted, suf) = unsafe { uiovec.align_to_mut::<Iovec>() }; | ||
if pre != [] || suf != [] { | ||
self.attacked(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let (pre, untrusted, suf) = unsafe { uiovec.align_to_mut::<Iovec>() }; | |
if pre != [] || suf != [] { | |
self.attacked(); | |
} | |
let (_, untrusted, _) = unsafe { uiovec.align_to_mut::<Iovec>() }; | |
if untrusted.len() != trusted.len() { | |
self.attacked(); | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does make way more sense as a method of checking the same thing. Thank you.
enarx-keep-sgx-shim/src/handler.rs
Outdated
let mut chunk = ubuffer.as_ptr(); | ||
for (t, mut u) in trusted.iter_mut().zip(untrusted.iter_mut()) { | ||
u.base = chunk as *mut u8; | ||
u.size = t.size; | ||
chunk = unsafe { chunk.add(t.size) }; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of converting the ubuffer
to a pointer and then doing pointer math, instead track the offset and convert a subslice to a pointer. That will remove the untrusted code block.
enarx-keep-sgx-shim/src/handler.rs
Outdated
let mut chunk = ubuffer.as_ptr(); | ||
for (t, u) in trusted.iter().zip(untrusted.iter()) { | ||
if u.base != chunk as *mut u8 || u.size != t.size { | ||
self.attacked(); | ||
} | ||
core::ptr::copy_nonoverlapping(u.base as *mut u8, t.base as *mut u8, ret as _); | ||
chunk = chunk.add(t.size); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above.
enarx-keep-sgx-shim/src/handler.rs
Outdated
self.ufree(map.as_ptr() as *mut u8, size as u64); | ||
} | ||
ret |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self.ufree(map.as_ptr() as *mut u8, size as u64); | |
} | |
ret | |
} | |
self.ufree(map.as_ptr() as *mut u8, size as u64); | |
ret |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This also makes lots more sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, I can't move this because ret
is known only within the unsafe
block.
enarx-keep-sgx-shim/src/handler.rs
Outdated
let ret = self.syscall( | ||
SysCall::READV, | ||
fd, | ||
untrusted.as_ptr() as _, | ||
trusted.len() as u64, | ||
0, | ||
0, | ||
0, | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let ret = self.syscall( | |
SysCall::READV, | |
fd, | |
untrusted.as_ptr() as _, | |
trusted.len() as u64, | |
0, | |
0, | |
0, | |
); | |
let ret = unsafe { | |
self.syscall( | |
SysCall::READV, | |
fd, | |
untrusted.as_ptr() as _, | |
trusted.len() as u64, | |
0, | |
0, | |
0, | |
) | |
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That enables you to reduce the size of the unsafe block significantly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right! Yes, I should have done this.
enarx-keep-sgx-shim/src/handler.rs
Outdated
|
||
let mut offset = 0; | ||
for (t, u) in trusted.iter().zip(untrusted.iter()) { | ||
if u.base != ubuffer[offset] as *mut u8 || u.size != t.size { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This cast doesn't do what you think it does. It converts the byte at offset to a *const u8
.
enarx-keep-sgx-shim/src/handler.rs
Outdated
if u.base != ubuffer[offset] as *mut u8 || u.size != t.size { | ||
self.attacked(); | ||
} | ||
core::ptr::copy_nonoverlapping(u.base as *mut u8, t.base as *mut u8, ret as _); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use the slice method copy_from_slice()
instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since u.base
is a *mut u8
, would I not have to turn it into a slice with the unsafe core::slice::from_raw_parts_mut
in order to use that method? That would result in the same number unsafe
lines here (1). Is there a better way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You know the offset into the ubuffer
, which is already a slice.
enarx-keep-sgx-shim/src/handler.rs
Outdated
let bufsize = trusted.iter().fold(0, |a, e| a + e.size); | ||
|
||
// Allocate some unencrypted memory. | ||
let size = bufsize + size_of::<Iovec>() * trusted.len(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
prefix = sizeof * len
enarx-keep-sgx-shim/src/handler.rs
Outdated
SysCall::READV, | ||
fd, | ||
untrusted.as_ptr() as _, | ||
trusted.len() as u64, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
trusted.len() as u64, | |
untrusted.len() as u64, |
These values are the same. But I think this is slightly more readable.
enarx-keep-sgx-shim/src/handler.rs
Outdated
base: *mut u8, | ||
/// Number of bytes to transfer | ||
size: usize, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This struct probably belongs in nolibc
.
7a5cee7
to
f46ddc3
Compare
054b3e2
to
dcecb58
Compare
@npmccallum I put the I also went ahead and included the I am pretty sure since a Rust slice is a wrapper for a pointer that the way I copy them after converting to a slice from |
nolibc/src/lib.rs
Outdated
impl<'a, T> Iovec<'a, T> { | ||
pub fn as_slice(&self) -> &[T] { | ||
let slice: &[T] = self.into(); | ||
slice | ||
} | ||
|
||
pub fn as_mut_slice(&mut self) -> &mut [T] { | ||
let slice: &mut [T] = self.into(); | ||
slice | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
impl AsRef<[u8]>
and impl AsMut<[u8]>
nolibc/src/lib.rs
Outdated
/// Buffer used by readv() and writev() syscalls | ||
#[repr(C)] | ||
pub struct Iovec<'a, T: 'a> { | ||
/// Buffer start address | ||
pub base: *mut T, | ||
|
||
/// Number of bytes to transfer | ||
pub size: usize, | ||
|
||
phantom: core::marker::PhantomData<&'a T>, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// Buffer used by readv() and writev() syscalls | |
#[repr(C)] | |
pub struct Iovec<'a, T: 'a> { | |
/// Buffer start address | |
pub base: *mut T, | |
/// Number of bytes to transfer | |
pub size: usize, | |
phantom: core::marker::PhantomData<&'a T>, | |
} | |
/// Buffer used by readv() and writev() syscalls | |
#[repr(C)] | |
pub struct Iovec<'a> { | |
/// Buffer start address | |
pub base: *mut u8, | |
/// Number of bytes to transfer | |
pub size: usize, | |
phantom: core::marker::PhantomData<&'a T>, | |
} |
That doesn't do what you think it does.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to learn more about this, obviously. I thought it would tie the lifetime to the base
. For now, Rust won't let me compile it without the T
in the struct parameters: pub struct Iovec<'a, T>
since it's required in PhantomData
. I changed the T
in the base to a u8
.
enarx-keep-sgx-shim/src/handler.rs
Outdated
let mut offset = 0; | ||
for (t, mut u) in trusted.iter_mut().zip(untrusted.iter_mut()) { | ||
u.base = ubuffer[offset..].as_mut_ptr(); | ||
u.size = t.size; | ||
offset += t.size; | ||
} | ||
|
||
// Copy the encrypted input into unencrypted memory. | ||
let mut offset = 0; | ||
for (t, u) in trusted.iter_mut().zip(untrusted.iter_mut()) { | ||
if u.base != ubuffer[offset..].as_mut_ptr() || u.size != t.size { | ||
self.attacked(); | ||
} | ||
|
||
u.as_mut_slice().copy_from_slice(t.as_mut_slice()); | ||
offset += t.size; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need two loops in this case. Also, we can drop the attacked check.
enarx-keep-sgx-shim/src/handler.rs
Outdated
self.attacked() | ||
} | ||
|
||
unsafe { self.ufree(map.as_ptr() as *mut u8, size as u64) }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move this to immediately after the syscall.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This mostly looks good. We're almost there!
nolibc/src/lib.rs
Outdated
#[repr(C)] | ||
pub struct Iovec<'a, T> { | ||
/// Buffer start address | ||
pub base: *mut u8, | ||
|
||
/// Number of bytes to transfer | ||
pub size: usize, | ||
|
||
phantom: core::marker::PhantomData<&'a T>, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#[repr(C)] | |
pub struct Iovec<'a, T> { | |
/// Buffer start address | |
pub base: *mut u8, | |
/// Number of bytes to transfer | |
pub size: usize, | |
phantom: core::marker::PhantomData<&'a T>, | |
} | |
#[repr(C)] | |
pub struct Iovec<'a> { | |
/// Buffer start address | |
pub base: *mut u8, | |
/// Number of bytes to transfer | |
pub size: usize, | |
phantom: core::marker::PhantomData<&'a ()>, | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't realize one could do this. Seems much better!
enarx-keep-sgx-shim/src/handler.rs
Outdated
self.attacked(); | ||
} | ||
|
||
t.as_mut().copy_from_slice(u.as_mut()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
t.as_mut().copy_from_slice(u.as_mut()); | |
t.as_mut().copy_from_slice(u.as_ref()); |
enarx-keep-sgx-shim/src/handler.rs
Outdated
// Set pointers in unencrypted iovec slice to use the rest of the allocated memory, | ||
// then copy the encrypted input into unencrypted memory. | ||
// The offset is into the buffer area allocated immediately after the iovec struct | ||
// array, measured in bytes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Properly wrap the paragraph.
enarx-keep-sgx-shim/src/handler.rs
Outdated
for (t, mut u) in trusted.iter_mut().zip(untrusted.iter_mut()) { | ||
u.base = ubuffer[offset..].as_mut_ptr(); | ||
u.size = t.size; | ||
u.as_mut().copy_from_slice(t.as_mut()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
u.as_mut().copy_from_slice(t.as_mut()); | |
u.as_mut().copy_from_slice(t.as_ref()); |
5131e47
to
9b839f2
Compare
Check that the memory allocated is page-aligned, and that the end of the returned memory is not inside the enclave.
This is a draft. In particular, it needs some help with casting pointers in a safe and correct way.
Resolves #375 and resolves #376