Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 56 additions & 32 deletions src/portal/portal_devfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -145,54 +145,67 @@ static void igloo_devfs_flush_shm_to_hypervisor(struct file *file, struct portal
{
void *buffer;
loff_t size;
ssize_t bytes;
loff_t pos = 0;
loff_t off;
size_t stage_sz;

if (!pe || !pe->shm_file) return;
if (!file->f_op || !file->f_op->write) return; // Python didn't provide a write hook

mutex_lock(&pe->shm_lock);

// Get the exact size of the shared memory file
size = i_size_read(file_inode(pe->shm_file));
if (size <= 0) goto unlock;

// Flush back in bounded chunks. The mmap length is guest-controlled, so a single
// (k)vzalloc(size) is an unbounded allocation that fails on 32-bit donor kernels for
// large device mmaps (vmalloc space is only ~128-240 MB) and lets a guest force huge
// kernel allocations on demand. A fixed staging buffer keeps this O(1) in kernel memory.
stage_sz = (size < (loff_t)IGLOO_DEVFS_STAGE_SZ) ? (size_t)size : IGLOO_DEVFS_STAGE_SZ;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)
buffer = kvzalloc(size, GFP_KERNEL);
buffer = kvzalloc(stage_sz, GFP_KERNEL);
#else
buffer = vzalloc(size);
buffer = vzalloc(stage_sz);
#endif
if (!buffer) goto unlock;

for (off = 0; off < size; off += stage_sz) {
size_t chunk = (size - off < (loff_t)stage_sz) ? (size_t)(size - off) : stage_sz;
loff_t rpos = off;
loff_t wpos = off;
ssize_t bytes;

if (buffer) {
// 1. Read the modified data from the hidden RAM file
// 1. Read this window of modified data from the hidden RAM file
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)
bytes = kernel_read(pe->shm_file, buffer, size, &pos);
bytes = kernel_read(pe->shm_file, buffer, chunk, &rpos);
#else
mm_segment_t old_fs = get_fs();
set_fs(KERNEL_DS);
bytes = vfs_read(pe->shm_file, (char __user *)buffer, size, &pos);
set_fs(old_fs);
{
mm_segment_t old_fs = get_fs();
set_fs(KERNEL_DS);
bytes = vfs_read(pe->shm_file, (char __user *)buffer, chunk, &rpos);
set_fs(old_fs);
}
#endif
if (bytes <= 0) break;

// 2. Push it back to the Python plugin via the trampoline
if (bytes > 0) {
pos = 0;
// 2. Push it back to the Python plugin via the trampoline (at the same offset)
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 0)
old_fs = get_fs();
{
mm_segment_t old_fs = get_fs();
set_fs(KERNEL_DS);
file->f_op->write(file, (const char __user *)buffer, bytes, &pos);
file->f_op->write(file, (const char __user *)buffer, bytes, &wpos);
set_fs(old_fs);
}
#else
file->f_op->write(file, (const char __user *)buffer, bytes, &pos);
file->f_op->write(file, (const char __user *)buffer, bytes, &wpos);
#endif
}
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)
kvfree(buffer);
kvfree(buffer);
#else
vfree(buffer);
vfree(buffer);
#endif
}

unlock:
mutex_unlock(&pe->shm_lock);
Expand Down Expand Up @@ -254,23 +267,34 @@ static int igloo_devfs_proxy_mmap(struct file *file, struct vm_area_struct *vma)
#endif

if (!IS_ERR(shm_file)) {
// Seed the sparse shmem backing in bounded chunks. The mmap length is
// guest-controlled, so a single (k)vzalloc(size) is unbounded and fails on
// 32-bit donor kernels for large device mmaps (issue #79). igloo_fetch_mmap_page()
// honors the offset, so a fixed staging buffer suffices regardless of mmap length.
size_t stage_sz = (size < IGLOO_DEVFS_STAGE_SZ) ? size : (size_t)IGLOO_DEVFS_STAGE_SZ;
void *buffer;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)
void *buffer = kvzalloc(size, GFP_KERNEL);
buffer = kvzalloc(stage_sz, GFP_KERNEL);
#else
void *buffer = vzalloc(size);
buffer = vzalloc(stage_sz);
#endif
if (buffer) {
ssize_t bytes = igloo_fetch_mmap_page(file, buffer, 0, size);
if (bytes > 0) {
loff_t pos = 0;
size_t off;
for (off = 0; off < size; off += stage_sz) {
size_t chunk = (size - off < stage_sz) ? (size - off) : stage_sz;
ssize_t bytes = igloo_fetch_mmap_page(file, buffer, off, chunk);
if (bytes > 0) {
loff_t pos = off;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)
kernel_write(shm_file, buffer, bytes, &pos);
kernel_write(shm_file, buffer, bytes, &pos);
#else
mm_segment_t old_fs = get_fs();
set_fs(KERNEL_DS);
vfs_write(shm_file, (char __user *)buffer, bytes, &pos);
set_fs(old_fs);
mm_segment_t old_fs = get_fs();
set_fs(KERNEL_DS);
vfs_write(shm_file, (char __user *)buffer, bytes, &pos);
set_fs(old_fs);
#endif
}
if (bytes <= 0) break; // stop on short/failed fetch
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)
kvfree(buffer);
Expand Down
6 changes: 6 additions & 0 deletions src/portal/portal_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ bool igloo_is_kernel_addr(unsigned long addr);

#define CHUNK_SIZE (PAGE_SIZE - sizeof(region_header))

// Fixed staging-buffer size for seeding/flushing mmap'd pseudofiles. The shmem backing
// is sparse, so the seed/flush trampoline copies in bounded chunks instead of allocating
// a single buffer the size of the (guest-controlled) mmap length. This keeps kernel memory
// O(1) regardless of mmap size and avoids unbounded (k)vzalloc() failures on 32-bit donors.
#define IGLOO_DEVFS_STAGE_SZ (64 * 1024)

// Define handler function type
typedef void (*portal_op_handler)(portal_region *mem_region);

Expand Down
34 changes: 22 additions & 12 deletions src/portal/portal_procfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -121,25 +121,35 @@ int igloo_proxy_mmap(struct file *file, struct vm_area_struct *vma)
#endif

if (!IS_ERR(shm_file)) {
// kvzalloc introduced in 4.12. Fall back to vzalloc for older kernels.
// Seed the sparse shmem backing in bounded chunks. The mmap length is
// guest-controlled, so a single (k)vzalloc(size) is unbounded and fails on
// 32-bit donor kernels for large device mmaps (issue #79). igloo_fetch_mmap_page()
// honors the offset, so a fixed staging buffer suffices regardless of mmap length.
size_t stage_sz = (size < IGLOO_DEVFS_STAGE_SZ) ? size : (size_t)IGLOO_DEVFS_STAGE_SZ;
void *buffer;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)
void *buffer = kvzalloc(size, GFP_KERNEL);
buffer = kvzalloc(stage_sz, GFP_KERNEL);
#else
void *buffer = vzalloc(size);
buffer = vzalloc(stage_sz);
#endif
if (buffer) {
ssize_t bytes = igloo_fetch_mmap_page(file, buffer, 0, size);
if (bytes > 0) {
loff_t pos = 0;
size_t off;
for (off = 0; off < size; off += stage_sz) {
size_t chunk = (size - off < stage_sz) ? (size - off) : stage_sz;
ssize_t bytes = igloo_fetch_mmap_page(file, buffer, off, chunk);
if (bytes > 0) {
loff_t pos = off;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)
kernel_write(shm_file, buffer, bytes, &pos);
kernel_write(shm_file, buffer, bytes, &pos);
#else
// FIX: Use vfs_write to safely handle the write vs write_iter abstraction
mm_segment_t old_fs = get_fs();
set_fs(KERNEL_DS);
vfs_write(shm_file, (char __user *)buffer, bytes, &pos);
set_fs(old_fs);
// FIX: Use vfs_write to safely handle the write vs write_iter abstraction
mm_segment_t old_fs = get_fs();
set_fs(KERNEL_DS);
vfs_write(shm_file, (char __user *)buffer, bytes, &pos);
set_fs(old_fs);
#endif
}
if (bytes <= 0) break; // stop on short/failed fetch
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)
kvfree(buffer);
Expand Down
Loading