Repository navigation
Keep rlimits in guest state and enforce them #435
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
c8b6f6c
f66950b
fe2d66d
6c46152
e333046
285f403
7d9b12c
e9f0cb3
8ea0d0c
07635ea
274a0b3
e2e7bdb
9b1beb3
8ddd275
42238e7
b33e3a2
c7e4572
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -249,7 +249,8 @@ endef | |
| CHECK_HOST_UNIT_BINS := $(addprefix $(BUILD_DIR)/, \ | ||
| test-tlbi-encoder-host test-fork-ipc-protocol-host \ | ||
| test-vcpu-run-hooks-host test-identity-override-host \ | ||
| test-teardown-live-vcpu-host test-stdio-nonblock-host test-casefold-host \ | ||
| test-teardown-live-vcpu-host test-stdio-nonblock-host \ | ||
| test-rlimit-unchanged-host test-casefold-host \ | ||
| test-casefold-walk-host test-absock-names-host \ | ||
| test-dynamic-array-host test-string-builder-host \ | ||
| test-wakeup-pipe-host test-guest-env-host \ | ||
|
|
@@ -273,6 +274,7 @@ $(call run-host-unit,test-dynamic-array-host,dynamic array unit test) | |
| $(call run-host-unit,test-string-builder-host,string builder unit test) | ||
| $(call run-host-unit,test-wakeup-pipe-host,wakeup pipe concurrency unit test) | ||
| $(call run-host-unit,test-stdio-nonblock-host,launcher stdio flags across a guest) | ||
| $(call run-host-unit,test-rlimit-unchanged-host,host rlimits across a guest) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: This lane silently skips the rlimit check when Prompt for AI agents
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not changed in this PR. test-rlimit-unchanged-host finds its guest the way the host test beside it does: test-stdio-nonblock-host also hard-codes build/elfuse and build/test-pipe-steal and prints SKIP when either is missing. Pointing the host tests at TEST_DIR under GUEST_TEST_BINARIES is a change to all of them, not to this one. |
||
| $(call run-host-unit,test-guest-env-host,guest environment merge cross product) | ||
| $(call run-host-unit,test-usb-desc-host,USB descriptor blob walk unit test) | ||
| $(call run-host-unit,test-usbdev-urb-host,usbdevfs URB bookkeeping unit test) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,9 +32,11 @@ | |
| #include "syscall/linux-wire.h" | ||
| #include "syscall/fuse.h" | ||
| #include "syscall/internal.h" | ||
| #include "syscall/mem.h" | ||
| #include "syscall/path.h" | ||
| #include "syscall/proc.h" | ||
| #include "syscall/signal.h" | ||
| #include "syscall/sys.h" | ||
|
|
||
| #include "debug/log.h" | ||
|
|
||
|
|
@@ -94,18 +96,16 @@ static void register_elf_segment_regions(guest_t *g, | |
| uint64_t seg_start = info->segments[i].gpa + va_load_base; | ||
| uint64_t seg_end = seg_start + info->segments[i].memsz; | ||
| uint64_t seg_gpa = info->segments[i].gpa + gpa_load_base; | ||
| guest_region_add_ex_gpa(g, seg_start, seg_end, seg_gpa, | ||
| elf_pf_to_prot(info->segments[i].flags), | ||
| LINUX_MAP_PRIVATE, info->segments[i].offset, | ||
| path, -1); | ||
| guest_region_add_elf_segment(g, seg_start, seg_end, seg_gpa, | ||
| elf_pf_to_prot(info->segments[i].flags), | ||
| info->segments[i].offset, path); | ||
| } | ||
| } | ||
|
|
||
| /* Publish shim, shim-data, heap, stack-guard, and stack regions to | ||
| * /proc/self/maps view, and invalidate the null page and stack-guard PTEs. | ||
| * Shared by guest_bootstrap_prepare and guest_bootstrap_rosetta_post_reset; the | ||
| * caller registers ELF or rosetta segments separately because those differ | ||
| * between aarch64 and rosetta guests. | ||
| /* Publish shim, shim-data, heap, and stack regions to /proc/self/maps view, and | ||
| * invalidate the null page PTEs. Shared by guest_bootstrap_prepare and | ||
| * guest_bootstrap_rosetta_post_reset; the caller registers ELF or rosetta | ||
| * segments separately because those differ between aarch64 and rosetta guests. | ||
| */ | ||
| static void register_runtime_regions(guest_t *g, size_t shim_bin_len) | ||
| { | ||
|
|
@@ -122,17 +122,23 @@ static void register_runtime_regions(guest_t *g, size_t shim_bin_len) | |
| guest_region_add(g, g->shim_data_base, g->shim_data_base + BLOCK_2MIB, | ||
| LINUX_PROT_NONE, LINUX_MAP_PRIVATE, 0, "[shim-data]"); | ||
|
|
||
| /* The vDSO page, which Linux lists as [vdso]. Tracking it also keeps a | ||
| * hinted mmap from being placed over it. | ||
| */ | ||
| guest_region_add(g, VDSO_BASE, VDSO_BASE + VDSO_SIZE, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Bootstrap adds Prompt for AI agents
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The overlap needs a PT_LOAD that covers VDSO_BASE (0xF000), and such an image does not run under elfuse before or after this change: vdso_build writes the vDSO over the image’s text at VDSO_BASE, and if the image starts at 0x0, the unmapped null page also removes its ELF header. I checked with a static binary linked at 0x0. It faults at 0x40 on this branch and on ba4cc0f, while Linux runs it when run as root. Rejecting or reserving the range here would only change how that image fails. The underlying gap is the fixed VDSO_BASE; running low-linked images is tracked in #440. |
||
| LINUX_PROT_READ | LINUX_PROT_EXEC, LINUX_MAP_PRIVATE, 0, | ||
| "[vdso]"); | ||
|
|
||
| if (g->brk_base < g->brk_current) { | ||
| guest_region_add(g, g->brk_base, g->brk_current, | ||
| LINUX_PROT_READ | LINUX_PROT_WRITE, | ||
| LINUX_MAP_PRIVATE | LINUX_MAP_ANONYMOUS, 0, "[heap]"); | ||
| } | ||
|
|
||
| guest_invalidate_ptes(g, g->stack_base, g->stack_base + STACK_GUARD_SIZE); | ||
| guest_region_add(g, g->stack_base, g->stack_base + STACK_GUARD_SIZE, | ||
| LINUX_PROT_NONE, LINUX_MAP_PRIVATE | LINUX_MAP_ANONYMOUS, | ||
| 0, "[stack-guard]"); | ||
| guest_region_add(g, g->stack_base + STACK_GUARD_SIZE, g->stack_top, | ||
| /* The whole window the image is built in; mem_stack_init_extent cuts it | ||
| * down to what Linux maps at execve once the stack pointer is known. | ||
| */ | ||
| guest_region_add(g, g->stack_base, g->stack_top, | ||
| LINUX_PROT_READ | LINUX_PROT_WRITE, | ||
| LINUX_MAP_PRIVATE | LINUX_MAP_ANONYMOUS, 0, "[stack]"); | ||
| guest_invalidate_ptes(g, 0, 0x1000); | ||
|
|
@@ -583,6 +589,7 @@ int guest_bootstrap_prepare(guest_t *g, | |
| t0 = startup_trace_now_ns(); | ||
| syscall_init(); | ||
| proc_init(); | ||
| sys_rlimit_init(); | ||
|
|
||
| { | ||
| char self_path[LINUX_PATH_MAX]; | ||
|
|
@@ -644,6 +651,7 @@ int guest_bootstrap_prepare(guest_t *g, | |
| return -1; | ||
| } | ||
| g->start_stack = boot->stack_pointer; | ||
| mem_stack_init_extent(g, boot->stack_pointer); | ||
| startup_trace_step("build_linux_stack", t0); | ||
|
|
||
| /* rosetta_argv was copied into the guest stack; the host allocation is no | ||
|
|
@@ -872,7 +880,7 @@ int guest_bootstrap_rosetta_post_reset(guest_t *g, | |
|
|
||
| /* Re-publish /proc/self/maps style metadata. Mirrors the bootstrap path so | ||
| * the post-exec view reports rosetta-as-anonymous-mapping plus the heap, | ||
| * stack, stack-guard, shim, and shim-data. | ||
| * stack, shim, and shim-data. | ||
| */ | ||
| register_elf_segment_regions(g, &rr.rosetta_info, 0, | ||
| g->rosetta_guest_base - g->rosetta_va_base, | ||
|
|
@@ -905,6 +913,7 @@ int guest_bootstrap_rosetta_post_reset(guest_t *g, | |
| return -1; | ||
| } | ||
| g->start_stack = sp; | ||
| mem_stack_init_extent(g, sp); | ||
| proc_set_auxv(auxv.words, auxv.nwords * sizeof(auxv.words[0])); | ||
|
|
||
| *out_entry_point = rr.entry_point; | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -48,6 +48,7 @@ | |||||||||||||||||||
| #include "utils.h" | ||||||||||||||||||||
| #include "runtime/futex.h" /* futex_interrupt_request */ | ||||||||||||||||||||
| #include "runtime/thread.h" /* thread_destroy_all_vcpus */ | ||||||||||||||||||||
| #include "syscall/mem.h" /* mem_stack_grow_for_host */ | ||||||||||||||||||||
| #include "syscall/proc.h" /* proc_request_exit_group */ | ||||||||||||||||||||
| #include "syscall/signal.h" | ||||||||||||||||||||
| #include "syscall/wakeup-pipe.h" | ||||||||||||||||||||
|
|
@@ -1619,6 +1620,15 @@ static uint64_t gva_contiguous_avail(const guest_t *g, | |||||||||||||||||||
| return total; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| bool guest_range_any_mapped(const guest_t *g, uint64_t start, uint64_t end) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| gva_translation_t t; | ||||||||||||||||||||
| for (uint64_t off = start; off < end; off += GUEST_PAGE_SIZE) | ||||||||||||||||||||
| if (gva_translate_perm(g, g->ipa_base + off, 0, &t) == 0) | ||||||||||||||||||||
| return true; | ||||||||||||||||||||
| return false; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /* Resolve a guest virtual address to a host pointer and available size. | ||||||||||||||||||||
| * Returns NULL if the address is not in any mapping. | ||||||||||||||||||||
| * | ||||||||||||||||||||
|
|
@@ -1637,10 +1647,20 @@ static void *gva_resolve_perm(const guest_t *g, | |||||||||||||||||||
| * identity-mapped (GVA == GPA == offset), but L2 block descriptors carry | ||||||||||||||||||||
| * permission bits and L3 page tables have per-4KiB permissions after | ||||||||||||||||||||
| * guest_split_block. Skipping the walk would bypass W^X enforcement for all | ||||||||||||||||||||
| * normal guest addresses. | ||||||||||||||||||||
| */ | ||||||||||||||||||||
| * normal guest addresses. An address just below the stack is not a miss | ||||||||||||||||||||
| * until the stack has refused to grow to it: Linux grows the stack for a | ||||||||||||||||||||
| * kernel access the way it does for a guest one, so read(2) into a stack | ||||||||||||||||||||
| * buffer the guest has not touched yet succeeds. Growing writes page tables | ||||||||||||||||||||
| * and the region table, which is why the guest is not const there; every | ||||||||||||||||||||
| * guest_t this is called with is a writable object. | ||||||||||||||||||||
| */ | ||||||||||||||||||||
| #ifdef ELFUSE_LOCK_ASSERT | ||||||||||||||||||||
| mmap_lock_assert_guest_access(); | ||||||||||||||||||||
| #endif | ||||||||||||||||||||
| gva_translation_t first; | ||||||||||||||||||||
| if (gva_translate_perm(g, gva, required_perms, &first) < 0) | ||||||||||||||||||||
| if (gva_translate_perm(g, gva, required_perms, &first) < 0 && | ||||||||||||||||||||
| (!mem_stack_grow_for_host((guest_t *) g, gva) || | ||||||||||||||||||||
| gva_translate_perm(g, gva, required_perms, &first) < 0)) | ||||||||||||||||||||
| return NULL; | ||||||||||||||||||||
|
Comment on lines
+1661
to
1664
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This returns
Always retry the translation after the grow attempt. Treat lock contention as "wait", not "refuse", at least on paths that hold no lock ranked below
Suggested change
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The sibling-growth case is fixed in 09932ef: Contention is changed in 2ba2fb0. The wait is chosen by the caller instead of being a 1 s trylock everywhere. The lock order in |
||||||||||||||||||||
|
|
||||||||||||||||||||
| /* Computed once and clamped once per branch below. Writing *avail twice | ||||||||||||||||||||
|
|
@@ -2005,10 +2025,19 @@ int guest_get_used_regions(const guest_t *g, | |||||||||||||||||||
| n++; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /* Stack (dynamic position, stored in guest_t) */ | ||||||||||||||||||||
| /* Stack (dynamic position, stored in guest_t): the default reserve, and | ||||||||||||||||||||
| * whatever a larger RLIMIT_STACK let it grow to below that. | ||||||||||||||||||||
| */ | ||||||||||||||||||||
| if (n < max) { | ||||||||||||||||||||
| out[n].offset = g->stack_base; | ||||||||||||||||||||
| out[n].size = g->stack_top - g->stack_base; | ||||||||||||||||||||
| uint64_t stack_lo = g->stack_base; | ||||||||||||||||||||
| for (int i = 0; i < g->nregions; i++) { | ||||||||||||||||||||
| const guest_region_t *r = &g->regions[i]; | ||||||||||||||||||||
| if (r->start < stack_lo && r->end <= g->stack_top && | ||||||||||||||||||||
|
xalestar marked this conversation as resolved.
|
||||||||||||||||||||
| !strcmp(r->name, "[stack]")) | ||||||||||||||||||||
| stack_lo = r->start; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| out[n].offset = stack_lo; | ||||||||||||||||||||
| out[n].size = g->stack_top - stack_lo; | ||||||||||||||||||||
| n++; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
@@ -2231,6 +2260,24 @@ int guest_region_add_ex_gpa(guest_t *g, | |||||||||||||||||||
| 0); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| int guest_region_add_elf_segment(guest_t *g, | ||||||||||||||||||||
| uint64_t start, | ||||||||||||||||||||
| uint64_t end, | ||||||||||||||||||||
| uint64_t gpa_base, | ||||||||||||||||||||
| int prot, | ||||||||||||||||||||
| uint64_t offset, | ||||||||||||||||||||
| const char *name) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| uint64_t lead = start & (GUEST_PAGE_SIZE - 1); | ||||||||||||||||||||
| start -= lead; | ||||||||||||||||||||
| end = PAGE_ALIGN_UP(end); | ||||||||||||||||||||
| if (guest_region_remove(g, start, end) < 0) | ||||||||||||||||||||
| return -1; | ||||||||||||||||||||
| return guest_region_add_ex_gpa( | ||||||||||||||||||||
| g, start, end, gpa_base - lead, prot, LINUX_MAP_PRIVATE, | ||||||||||||||||||||
| offset >= lead ? offset - lead : 0, name, -1); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| static uint64_t allocate_vma_id(guest_t *g) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| uint64_t candidate = g->next_vma_id; | ||||||||||||||||||||
|
|
@@ -3706,6 +3753,48 @@ int guest_install_va_pages(guest_t *g, | |||||||||||||||||||
| return 0; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| int guest_map_pages(guest_t *g, uint64_t start, uint64_t end, int perms) | ||||||||||||||||||||
|
xalestar marked this conversation as resolved.
|
||||||||||||||||||||
| { | ||||||||||||||||||||
| if (end <= start) | ||||||||||||||||||||
| return 0; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /* Only the two end blocks can be partly covered. One that has no mapping | ||||||||||||||||||||
| * yet is about to get a whole-block descriptor, so note it before the | ||||||||||||||||||||
| * extend and cut it back to the range after. | ||||||||||||||||||||
| */ | ||||||||||||||||||||
| uint64_t head = ALIGN_2MIB_DOWN(start), tail = ALIGN_2MIB_DOWN(end - 1); | ||||||||||||||||||||
| const uint64_t *head_l2 = find_l2_entry(g, head); | ||||||||||||||||||||
| const uint64_t *tail_l2 = find_l2_entry(g, tail); | ||||||||||||||||||||
| bool trim_head = start > head && !(head_l2 && (*head_l2 & PT_VALID)); | ||||||||||||||||||||
| bool trim_tail = | ||||||||||||||||||||
| end < tail + BLOCK_2MIB && !(tail_l2 && (*tail_l2 & PT_VALID)); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| int rc = guest_extend_page_tables(g, start, end, perms); | ||||||||||||||||||||
| if (rc == 0 && trim_head && | ||||||||||||||||||||
| (guest_split_block(g, head) < 0 || | ||||||||||||||||||||
| guest_invalidate_ptes(g, head, start) < 0)) | ||||||||||||||||||||
| rc = -1; | ||||||||||||||||||||
| if (rc == 0 && trim_tail && | ||||||||||||||||||||
| (guest_split_block(g, tail) < 0 || | ||||||||||||||||||||
| guest_invalidate_ptes(g, end, tail + BLOCK_2MIB) < 0)) | ||||||||||||||||||||
| rc = -1; | ||||||||||||||||||||
| if (rc == 0) | ||||||||||||||||||||
| rc = guest_update_perms(g, start, end, perms); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /* A failure can leave a fresh end block mapped whole, which is the | ||||||||||||||||||||
| * over-mapping this function exists to prevent. Such a block held nothing | ||||||||||||||||||||
| * before the call, so it is unmapped whole; clearing a whole block takes no | ||||||||||||||||||||
| * page-table page and so cannot fail the way a split does. | ||||||||||||||||||||
| */ | ||||||||||||||||||||
| if (rc < 0) { | ||||||||||||||||||||
| if (trim_head) | ||||||||||||||||||||
| (void) guest_invalidate_ptes(g, head, head + BLOCK_2MIB); | ||||||||||||||||||||
| if (trim_tail) | ||||||||||||||||||||
| (void) guest_invalidate_ptes(g, tail, tail + BLOCK_2MIB); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| return rc; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /* Lazy page materialization for MAP_NORESERVE. */ | ||||||||||||||||||||
|
|
||||||||||||||||||||
| int guest_materialize_lazy(guest_t *g, uint64_t fault_offset) | ||||||||||||||||||||
|
|
||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: This target does not rebuild when
tests/test-harness.hchanges, somake checkcan run a stale host-test binary. Add the header as a prerequisite.Prompt for AI agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not changed in this PR. The rule has the same shape as the host-test rules around it: test-stdio-nonblock-host, two rules up, also includes test-harness.h and lists only its .c file. Header prerequisites for the host tests belong in one change over all of those rules.