-
Notifications
You must be signed in to change notification settings - Fork 269
Improving conversions between SysCallReg and integers/pointers #2658
Description
When making a system call, the plugin writes a value of type A to a "register", and shadow's syscall handler reads a value of type B from the register. If types A and B don't match (and if they don't have the same width), shadow's syscall handler may use the wrong value. We currently have no way of detecting these mistakes, and there may not be any good way of detecting them. We should try to detect these mistakes if we can, and we should also try to prevent them.
There are a few cases to consider:
- Shadow reads a larger-width value: If the application writes an
-1i32to a syscall register, and Shadow's syscall handler does ai64::from(reg), Shadow may get a value of4_294_967_295i64, not-1i64since the value may not be sign extended to 64 bits. This is the case for glibc, but there is no guarentee that other libc implementations (or applications that don't use libc) haven't sign extended the value. - Shadow reads a smaller-width value: If the application writes a
u32::MAX as u64 + 1to a syscall register, and Shadow's syscall handler does au32::from(reg), Shadow will get a value of0, notu32::MAX + 1. - Shadow reads a value with a different signedness: If the application writes a
u32but Shadow's syscall handler reads it as ani32. - Shadow reads from a pointer of a different type: If the application writes a
*mut u32pointer to a syscall register, and Shadow's syscall handler writes to it as a*mut i64pointer.
Some examples where these types of mistakes may happen:
- When we write syscall handlers we tend to follow the libc API rather than the syscall API, so we could accidentally read a
SysCallRegas the wrong type if the libc call doesn't match the syscall API. - If we originally cast the register value to some type, but then we changed something else in Shadow which changed rust's type inference, which changed what type we cast the register value to. For example, if we originally had:
This would work fine because the first argument to
fn get_descriptor(i32) -> Option<Descriptor> { ... } fn syscall_write(ctx: &mut ThreadContext, args: &SysCallArgs) -> SyscallResult { let desc = get_descriptor(args.get(0).into()); [...] }
SYS_writeis anint, which is ani32. But later if we changed the definition ofget_descriptorto accept au32(for examplefn get_descriptor(u32) -> Option<Descriptor> { ... }), Shadow would still compile fine, but we'd begin incorrectly casting the register value to au32instead of ani32, even though we never touched the syscall handler code.
We try to limit these mistakes when converting register values to pointers by requiring an explicit type that Rust can't perform type inference for. We do this using a trait NoTypeInference. For example when you call TypedPluginPtr::new, you must explicitly pass the generic type using TypedPluginPtr::new::<u8>(...). If you leave out the generic type, Rust's type inference will fail. It may be useful to use this same idea for converting register values to integers as well, and removing the From/TryFrom implementations for SysCallReg.
It may also be useful to have some automated way of compiling a list of syscalls and their argument types, and making sure Shadow's syscall handlers cast the register values to the correct types.