Skip to content

Kernel RW fails if the address ends with 0xff #56

Description

@idlesauce

If the first byte of the in6_pktinfo/IPV6_PKTINFO is 0xff, then setsockopt fails with errno 22, the check responsible for this appears to be here: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/netinet6/ip6_output.c#L2693
Currently in kernel_copyin and kernel_copyout, at the second kernel_write call, the start of the in6_pktinfo buffer holds the kaddr argument, which may have 0xff as the lowest (little-endian first) byte, causing the kernel rw to fail.

Reproduction:

int main() {
    uint8_t fe_byte = 0;
    if (kernel_copyout(KERNEL_ADDRESS_DATA_BASE + 0xfe, &fe_byte, sizeof(fe_byte)) == 0) {
        puts("Successfully read from address ending in 0xfe");
    } else {
        puts("Failed to read from address ending in 0xfe");
    }

    uint8_t ff_byte = 0;
    if (kernel_copyout(KERNEL_ADDRESS_DATA_BASE + 0xff, &ff_byte, sizeof(ff_byte)) == 0) {
        puts("Successfully read from address ending in 0xff");
    } else {
        puts("Failed to read from address ending in 0xff");
    }

    if (kernel_copyin(&fe_byte, KERNEL_ADDRESS_DATA_BASE + 0xfe, sizeof(fe_byte)) == 0) {
        puts("Successfully wrote to address ending in 0xfe");
    } else {
        puts("Failed to write to address ending in 0xfe");
    }

    if (kernel_copyin(&ff_byte, KERNEL_ADDRESS_DATA_BASE + 0xff, sizeof(ff_byte)) == 0) {
        puts("Successfully wrote to address ending in 0xff");
    } else {
        puts("Failed to write to address ending in 0xff");
    }

    return 0;
}

This results in the following output:

Successfully read from address ending in 0xfe
Failed to read from address ending in 0xff
Successfully wrote to address ending in 0xfe
Failed to write to address ending in 0xff

Potential fix:

In kernel_copyout and kernel_copyin, have the second kernel_write call write at offset 12 instead of 16, ensuring that the first byte is never 0xff.
By doing this, we are no longer zeroing out the lower 32 bits of the next field, which should be pipe_map.pos, but given it was only partially zeroed out before, and we actively need to avoid PIPE_DIRECTW for kernel rw, i believe this should be fine, but this should be double checked.

int
kernel_copyout(unsigned long kaddr, void *uaddr, unsigned long len) {
  unsigned int write_buf[5]; // sizeof in6_pktinfo

  if(!kaddr || !uaddr || !len) {
    SET_ERRNO(EINVAL);
    return -1;
  }

  // Set pipe flags
  write_buf[0] = 0x40000000; // pipe_buffer.cnt
  write_buf[1] = 0x40000000; // pipe_buffer.in
  write_buf[2] = 0;          // pipe_buffer.out
  write_buf[3] = 0;          // set by next kernel_write
  write_buf[4] = 0;          // set by next kernel_write
  if(kernel_write(pipe_addr, (unsigned long *) &write_buf)) {
    return -1;
  }

  // Set pipe data addr
  write_buf[0] = 0x40000000;  // pipe_buffer.size
  write_buf[1] = kaddr;       // pipe_buffer.buffer lower 32 bits
  write_buf[2] = kaddr >> 32; // pipe_buffer.buffer upper 32 bits
  write_buf[3] = 0;           // pipe_map.cnt lower 32 bits
  write_buf[4] = 0;           // pipe_map.cnt upper 32 bits
  if(kernel_write(pipe_addr + 12, (unsigned long *) &write_buf)) {
    return -1;
  }

  // Perform read across pipe
  if(__crt_syscall(SYS_read, rw_pipe[0], uaddr, len) < 0) {
    return -1;
  }

  return 0;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions