Skip to content

Improving conversions between SysCallReg and integers/pointers #2658

@stevenengler

Description

@stevenengler

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:

  1. Shadow reads a larger-width value: If the application writes an -1i32 to a syscall register, and Shadow's syscall handler does a i64::from(reg), Shadow may get a value of 4_294_967_295i64, not -1i64 since 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.
  2. Shadow reads a smaller-width value: If the application writes a u32::MAX as u64 + 1 to a syscall register, and Shadow's syscall handler does a u32::from(reg), Shadow will get a value of 0, not u32::MAX + 1.
  3. Shadow reads a value with a different signedness: If the application writes a u32 but Shadow's syscall handler reads it as an i32.
  4. Shadow reads from a pointer of a different type: If the application writes a *mut u32 pointer to a syscall register, and Shadow's syscall handler writes to it as a *mut i64 pointer.

Some examples where these types of mistakes may happen:

  1. When we write syscall handlers we tend to follow the libc API rather than the syscall API, so we could accidentally read a SysCallReg as the wrong type if the libc call doesn't match the syscall API.
  2. 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:
    fn get_descriptor(i32) -> Option<Descriptor> { ... }
    fn syscall_write(ctx: &mut ThreadContext, args: &SysCallArgs) -> SyscallResult {
        let desc = get_descriptor(args.get(0).into());
        [...]
    }
    This would work fine because the first argument to SYS_write is an int, which is an i32. But later if we changed the definition of get_descriptor to accept a u32 (for example fn get_descriptor(u32) -> Option<Descriptor> { ... }), Shadow would still compile fine, but we'd begin incorrectly casting the register value to a u32 instead of an i32, 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type: MaintenanceRefactoring, cleanup, documenation, or process improvements

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions