-
Notifications
You must be signed in to change notification settings - Fork 126
fix(starry-mm): mprotect returns ENOMEM on unmapped holes within the range #918
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
Changes from all commits
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 |
|---|---|---|
|
|
@@ -450,8 +450,16 @@ pub fn sys_mprotect(addr: usize, length: usize, prot: u32) -> AxResult<isize> { | |
| let mut aspace = aspace_arc.lock(); | ||
| let length = align_up_4k(length); | ||
| let start_addr = VirtAddr::from(addr); | ||
| // man 2 mprotect: addresses without a mapping → ENOMEM. | ||
| if aspace.find_area(start_addr).is_none() { | ||
| // man 2 mprotect: if any page in [addr, addr+len) lacks a mapping → ENOMEM. | ||
| // Linux validates the whole range, not just the first page: an unmapped hole | ||
| // in the middle of `[mapped][hole][mapped]` must fail instead of silently | ||
| // protecting only the mapped fragments. `can_access_range` with an empty | ||
| // access mask is a pure contiguous-coverage check (every area's flags | ||
| // trivially contain the empty set), so it returns false on the first gap. | ||
| // We pre-check and leave the mapping untouched on failure, i.e. report | ||
| // ENOMEM atomically without any half-applied protection — the errno real | ||
| // programs test for. | ||
| if !aspace.can_access_range(start_addr, length, MappingFlags::empty()) { | ||
|
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. ✅
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. ✅ |
||
| return Err(AxError::NoMemory); | ||
| } | ||
| if permission_flags.contains(MmapProt::WRITE) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,8 @@ | |
| #include <unistd.h> | ||
| #include <fcntl.h> | ||
| #include <string.h> | ||
| #include <signal.h> | ||
| #include <setjmp.h> | ||
|
|
||
| #ifndef PROT_GROWSDOWN | ||
| #define PROT_GROWSDOWN 0x01000000 | ||
|
|
@@ -42,6 +44,28 @@ | |
| } \ | ||
| } while(0) | ||
|
|
||
| /* 写一字节到 *p, 返回是否触发 SIGSEGV (用于验证某页是否仍可写)。 */ | ||
| static sigjmp_buf g_jb; | ||
| static volatile int g_faulted; | ||
| static void on_segv(int sig) | ||
| { | ||
| (void)sig; | ||
| g_faulted = 1; | ||
| siglongjmp(g_jb, 1); | ||
| } | ||
| static int write_faults(volatile char *p) | ||
| { | ||
| struct sigaction sa = {0}, old; | ||
| sa.sa_handler = on_segv; | ||
| sigaction(SIGSEGV, &sa, &old); | ||
| g_faulted = 0; | ||
| if (sigsetjmp(g_jb, 1) == 0) { | ||
| *p = 0x55; | ||
| } | ||
| sigaction(SIGSEGV, &old, NULL); | ||
| return g_faulted; | ||
| } | ||
|
|
||
| int main(void) | ||
| { | ||
| TEST_START("mmap/munmap/mprotect"); | ||
|
|
@@ -128,6 +152,34 @@ int main(void) | |
| CHECK_ERR(mprotect(gone, ps, PROT_READ), | ||
| ENOMEM, "mprotect 未映射区间 → ENOMEM"); | ||
|
|
||
| /* mprotect 跨 [mapped][hole][mapped] → ENOMEM (回归: 起始页有映射但区间 | ||
| * 中段是空洞)。man 2 mprotect: 区间内任一页未映射即 ENOMEM。旧 starry 仅 | ||
| * 检查起始页所在 VMA (find_area(start) 命中) 便放行, 静默成功跳过空洞; | ||
| * 修复后用 can_access_range 做整段连续覆盖校验。*/ | ||
| { | ||
| char *hole = mmap(NULL, 3 * ps, PROT_READ | PROT_WRITE, | ||
| MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); | ||
| CHECK(hole != MAP_FAILED, "mmap 3 页用于 hole-mprotect 测试"); | ||
| if (hole != MAP_FAILED) { | ||
| /* 在中间页打一个洞, 留下 [mapped][hole][mapped] */ | ||
| CHECK_RET(munmap(hole + ps, ps), 0, "munmap 中间页造洞"); | ||
| CHECK_ERR(mprotect(hole, 3 * ps, PROT_READ), | ||
| ENOMEM, "mprotect 跨中段空洞 → ENOMEM"); | ||
|
ZR233 marked this conversation as resolved.
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. ✅ 注释也正确文档化了 StarryOS 的原子语义(先检查再改,全有或全无)与 Linux 的差异(先改前缀再返回 ENOMEM),避免后续维护者误以为这是 bug。 |
||
| /* 原子性: StarryOS 用 can_access_range 在改任何权限前先校验整段, | ||
| * 命中空洞即原子拒绝、一页都不改, 故左右两页保持原 RW。 | ||
| * 注: 真 Linux 在此并非原子 —— 它把保护位应用到空洞前的前缀页(左页 | ||
| * 会变成 PROT_READ)再返回 ENOMEM; StarryOS 选择更安全的原子语义。 | ||
| * 本断言锁住该原子行为: 若日后把 pre-check 挪到逐页 protect 之后, | ||
| * 左页会被半改成只读, 这里立刻抓住。*/ | ||
| CHECK(write_faults(hole) == 0, | ||
| "失败 mprotect 后左页仍可写 (StarryOS 原子拒绝, 未半改)"); | ||
| CHECK(write_faults(hole + 2 * ps) == 0, | ||
| "失败 mprotect 后右页仍可写 (空洞之后, 任何实现都不应改)"); | ||
| munmap(hole, ps); | ||
| munmap(hole + 2 * ps, ps); | ||
|
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. ✅ 测试结构正确:3 页映射 → munmap 中间页造洞 → mprotect 整段期望 ENOMEM。覆盖了旧实现 |
||
| } | ||
| } | ||
|
|
||
| /* ===================== munmap ===================== */ | ||
|
|
||
| /* happy path */ | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.