From afe695fefd51b55898074a0083cbf490943669de Mon Sep 17 00:00:00 2001 From: Luke Craig Date: Sun, 17 May 2026 17:03:14 -0400 Subject: [PATCH] fix(driver): handle riscv64 portal memory and trampolines --- src/portal/portal_mem.c | 74 ++++++++++++------------ src/portal/portal_osi.c | 118 ++++++++++++++++++++++++++++++++++---- src/portal/portal_tramp.c | 15 ++++- 3 files changed, 159 insertions(+), 48 deletions(-) diff --git a/src/portal/portal_mem.c b/src/portal/portal_mem.c index b21bd3d..f99821b 100644 --- a/src/portal/portal_mem.c +++ b/src/portal/portal_mem.c @@ -20,16 +20,12 @@ bool igloo_is_kernel_addr(unsigned long addr) return (addr >= PAGE_OFFSET); #elif defined(CONFIG_RISCV) - /* RISC-V kernel virtual address space check */ - #if defined(CONFIG_64BIT) - /* For RISC-V 64-bit, kernel addresses are in the upper half */ - /* Check if address is in kernel virtual address space */ - /* RISC-V uses canonical addresses where kernel space starts high */ - return (addr >= KERNEL_LINK_ADDR) || (addr >= PAGE_OFFSET); - #else - /* RISC-V 32-bit */ - return (addr >= PAGE_OFFSET); - #endif + /* + * RISC-V has multiple kernel virtual ranges. Kernel stacks can live below + * PAGE_OFFSET/KERNEL_LINK_ADDR (for example ff20... on the 6.13 virt + * kernels), so classify against the user/kernel split instead. + */ + return addr >= TASK_SIZE; #elif defined(CONFIG_PPC) return is_kernel_addr(addr); @@ -65,6 +61,24 @@ bool igloo_is_kernel_addr(unsigned long addr) #endif } +static long igloo_kernel_read_nofault(void *dst, const void *src, size_t size) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) + return copy_from_kernel_nofault(dst, src, size); +#else + return probe_kernel_read(dst, src, size); +#endif +} + +static long igloo_kernel_write_nofault(void *dst, const void *src, size_t size) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) + return copy_to_kernel_nofault(dst, src, size); +#else + return probe_kernel_write(dst, src, size); +#endif +} + struct task_struct *get_target_task_by_id(portal_region* mem_region) { pid_t target_pid = (pid_t)(mem_region->header.pid); @@ -102,27 +116,21 @@ void handle_op_read(portal_region *mem_region) (unsigned long long)addr, (unsigned long long)size); if (is_kernel_address) { - // Handle kernel memory - directly copy with memcpy + // Handle kernel memory, including stack/vmalloc/module addresses. igloo_pr_debug("igloo: Reading from kernel address %#lx, size %zu\n", addr, size); if (size > CHUNK_SIZE) { igloo_pr_debug("igloo: Requested size too large, truncating to %zu\n", (size_t)CHUNK_SIZE); size = CHUNK_SIZE; } - - // Only access memory we think is valid - if (virt_addr_valid((void *)addr)) { - // Use memcpy with safety precautions - unsigned long flags; - local_irq_save(flags); - pagefault_disable(); - memcpy(PORTAL_DATA(mem_region), (void *)addr, size); - pagefault_enable(); - local_irq_restore(flags); - + + resp = igloo_kernel_read_nofault(PORTAL_DATA(mem_region), (void *)addr, size); + if (resp == 0) { mem_region->header.op = HYPER_RESP_READ_OK; mem_region->header.size = size; } else { - igloo_pr_debug("igloo: Invalid kernel address %#lx\n", addr); + igloo_pr_debug( + "igloo: kernel read failed for addr %#lx, size %zu, resp %d\n", + addr, size, resp); mem_region->header.op = HYPER_RESP_READ_FAIL; } } else { @@ -158,28 +166,22 @@ void handle_op_write(portal_region *mem_region) (unsigned long long)addr, (unsigned long long)size); if (igloo_is_kernel_addr(addr)) { - // Handle kernel memory writes - use memcpy, but with caution + // Handle kernel memory writes, including stack/vmalloc/module addresses. igloo_pr_debug("igloo: Writing to kernel address %#lx, size %zu\n", addr, size); if (size > CHUNK_SIZE) { igloo_pr_debug("igloo: Requested size too large, truncating to %zu\n", (size_t)CHUNK_SIZE); size = CHUNK_SIZE; } - - // Only write to memory we think is valid and writable - if (virt_addr_valid((void *)addr)) { - // Use memcpy with safety precautions - unsigned long flags; - local_irq_save(flags); - pagefault_disable(); - memcpy((void *)addr, PORTAL_DATA(mem_region), size); - pagefault_enable(); - local_irq_restore(flags); - + + resp = igloo_kernel_write_nofault((void *)addr, PORTAL_DATA(mem_region), size); + if (resp == 0) { igloo_pr_debug("igloo: Successfully wrote to kernel address %#lx\n", addr); mem_region->header.op = HYPER_RESP_WRITE_OK; } else { - igloo_pr_debug("igloo: Invalid kernel address %#lx\n", addr); + igloo_pr_debug( + "igloo: kernel write failed for addr %#lx, size %zu, resp %d\n", + addr, size, resp); mem_region->header.op = HYPER_RESP_WRITE_FAIL; } } else { diff --git a/src/portal/portal_osi.c b/src/portal/portal_osi.c index ff0c151..092729b 100644 --- a/src/portal/portal_osi.c +++ b/src/portal/portal_osi.c @@ -736,6 +736,7 @@ void handle_op_read_fds(portal_region *mem_region) int start_fd = 0; int i; size_t max_fds; + bool reached_end = false; char *data_buf = PORTAL_DATA(mem_region); struct osi_result_header *header = (struct osi_result_header *)data_buf; struct osi_fd_entry *fd_entry; @@ -844,16 +845,90 @@ void handle_op_read_fds(portal_region *mem_region) string_offset = sizeof(struct osi_result_header) + (max_fds * sizeof(struct osi_fd_entry)); string_buf = data_buf + string_offset; - // Access the process's file descriptor table +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0) + /* + * Modern fd tables must be walked through the exported helper. Raw fdtable + * entries are RCU-managed and can be stale by the time d_path() consumes + * them. + */ + { + unsigned int fd_cursor; + + for (fd_cursor = 0; ; fd_cursor++) { + file = fget_task_next(task, &fd_cursor); + if (!file) { + break; + } + total_count++; + fput(file); + } + header->total_count = total_count; + + for (fd_cursor = (unsigned int)start_fd; ; fd_cursor++) { + file = fget_task_next(task, &fd_cursor); + if (!file) { + reached_end = true; + break; + } + i = (int)fd_cursor; + + if (count >= max_fds || string_offset >= CHUNK_SIZE - PATH_MAX) { + mem_region->header.addr = i; + fput(file); + break; + } + + // Fill in fd entry + fd_entry->fd = (i); + fd_entry->name_offset = (string_offset); + + // Move to temporary string buffer for path + path_buf = kmalloc(PATH_MAX, GFP_KERNEL); + if (path_buf) { + char *path = d_path(&file->f_path, path_buf, PATH_MAX); + if (!IS_ERR(path)) { + size_t name_len = strlen(path); + + // Check if we have enough space for this name + if (string_offset + name_len + 1 <= CHUNK_SIZE) { + // Copy path to string buffer + strncpy(string_buf, path, name_len); + string_buf[name_len] = '\0'; + + // Update string buffer position + string_buf += name_len + 1; + string_offset += name_len + 1; + + // Increment counts + fd_entry++; + count++; + + igloo_debug_osi("igloo: Processed fd %d: %s\n", i, path); + } else { + // Not enough space for this entry's name + mem_region->header.addr = (i); + igloo_debug_osi("igloo: Not enough space for fd %d path, will continue from here next time\n", i); + kfree(path_buf); + fput(file); + break; + } + } + kfree(path_buf); + path_buf = NULL; + } + fput(file); + } + } +#else task_lock(task); - if (!task->files) { + files = task->files; + if (!files) { task_unlock(task); mem_region->header.size = (sizeof(struct osi_result_header)); mem_region->header.op = (HYPER_RESP_READ_OK); return; } - files = task->files; spin_lock(&files->file_lock); fdt = files_fdtable(files); @@ -865,31 +940,56 @@ void handle_op_read_fds(portal_region *mem_region) // Store total count in header header->total_count = (total_count); + spin_unlock(&files->file_lock); + task_unlock(task); // Start filling fd entries from start_fd - for (i = start_fd; i < fdt->max_fds; i++) { + for (i = start_fd; ; i++) { + task_lock(task); + files = task->files; + if (!files) { + reached_end = true; + task_unlock(task); + break; + } + + spin_lock(&files->file_lock); + fdt = files_fdtable(files); + if (i >= fdt->max_fds) { + reached_end = true; + spin_unlock(&files->file_lock); + task_unlock(task); + break; + } + file = fdt->fd[i]; if (!file){ + spin_unlock(&files->file_lock); + task_unlock(task); igloo_debug_osi("igloo: No file for fd %d\n", i); continue; } if (count >= max_fds || string_offset >= CHUNK_SIZE - PATH_MAX) { // Store the next fd number to start from in the next call - mem_region->header.addr = (i + 1); + mem_region->header.addr = i; + spin_unlock(&files->file_lock); + task_unlock(task); break; } // Get a reference to the file get_file(file); + spin_unlock(&files->file_lock); + task_unlock(task); // Fill in fd entry fd_entry->fd = (i); fd_entry->name_offset = (string_offset); // Move to temporary string buffer for path - path_buf = kmalloc(PATH_MAX, GFP_ATOMIC); + path_buf = kmalloc(PATH_MAX, GFP_KERNEL); if (path_buf) { char *path = d_path(&file->f_path, path_buf, PATH_MAX); if (!IS_ERR(path)) { @@ -923,11 +1023,10 @@ void handle_op_read_fds(portal_region *mem_region) // Release the file reference fput(file); } - spin_unlock(&files->file_lock); - task_unlock(task); +#endif // If we processed all FDs, set next count to 0 to indicate completion - if (i >= fdt->max_fds) { + if (reached_end) { mem_region->header.addr = 0; } @@ -981,4 +1080,3 @@ void handle_op_osi_proc_ptregs(portal_region *mem_region) igloo_debug_osi("igloo: ptregs pointer returned for current task: %p\n", regs); return; } - diff --git a/src/portal/portal_tramp.c b/src/portal/portal_tramp.c index d2079a4..c9373f1 100644 --- a/src/portal/portal_tramp.c +++ b/src/portal/portal_tramp.c @@ -13,11 +13,22 @@ /* We ALWAYS include kprobes as the universal fallback */ #include +/* + * The Python trampoline path consumes the second hypercall argument as a real + * struct pt_regs *. On riscv64, fprobe/ftrace on modern kernels passes an + * ftrace_regs object through this callback path, which is not layout-compatible + * with struct pt_regs and causes null/shifted callback arguments. Use kprobes + * there until we add a native ftrace_regs payload decoder. + */ +#if defined(CONFIG_RISCV) + #define USE_KPROBE_TRAMPOLINES +#endif + /* Try to include high-speed tracing mechanisms if the kernel version allows */ -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) && !defined(USE_KPROBE_TRAMPOLINES) #define USE_FPROBE #include -#elif defined(CONFIG_DYNAMIC_FTRACE_WITH_REGS) +#elif defined(CONFIG_DYNAMIC_FTRACE_WITH_REGS) && !defined(USE_KPROBE_TRAMPOLINES) #define USE_FTRACE #include #endif