fix(starry-mm): mprotect returns ENOMEM on unmapped holes within the range#918
Conversation
This comment was marked as duplicate.
This comment was marked as duplicate.
8d0c53a to
813b154
Compare
|
@codex review pr! |
There was a problem hiding this comment.
Review: PR #918 — mprotect 空洞覆盖检查
改动分析
本次修改将 sys_mprotect 的存在性检查从 find_area(start_addr)(只检查首页)替换为 can_access_range(start_addr, length, MappingFlags::empty()),对整个 [addr, addr+len) 范围做连续覆盖判定,与 Linux mprotect 行为对齐。
正确性评估 ✅
can_access_range遍历areas迭代器,逐区间推进range.start,遇到空洞立即返回false。MappingFlags::empty()作为空集是所有 flags 的子集,因此area.flags().contains(empty())恒为true——该调用等价于纯覆盖检查。- 检查在
protect()调用之前完成,失败时映射保持不变,满足原子性语义。 - 注释清晰,正确解释了
empty()的语义和 ENOMEM 的 POSIX/Linux 语义来源。
CI 状态
已完成的 job 全部 success:Detect changed paths、Check formatting、Run sync-lint、Test axvisor loongarch64 qemu (container)、Test axvisor x86_64 svm hosted (host)。其余多个 QEMU 测试 job 仍在运行中,尚未发现失败。
重复/重叠分析
本 PR 仅修改 mmap.rs 中的 sys_mprotect,与其他同期 open PR(#1119 netlink、#1118 ShmidDs、#1114 集合修复等)无交集。
测试覆盖
test-suit/starryos/normal/qemu-smp1/syscall/test-mmap-family 已有 mprotect ENOMEM 测试,但仅覆盖「首页完全未映射」场景。本次修复的「[mapped][hole][mapped] 空洞」场景目前无专门回归测试。建议后续补充一个 3 页映射 + 中间 munmap + mprotect 整段 → ENOMEM 的测试用例,以防止回归。
结论
修改逻辑正确、代码清晰、影响范围小,已通过已完成的 CI 检查。
APPROVE,建议后续跟进空洞场景的回归测试。
Powered by mimo-v2.5-pro
ZR233
left a comment
There was a problem hiding this comment.
这轮先请求修改。实现逻辑本身是对的:can_access_range(start, len, MappingFlags::empty()) 会把 mprotect 的存在性检查从“只看起始页”提升为整个区间的连续覆盖检查;由于检查发生在 protect() 前,失败路径也不会半应用新的权限,和 Linux 对区间内任意未映射页返回 ENOMEM 的语义一致。
阻塞项是缺少能复现本 bug 的回归测试。现有 test-mmap-family 只覆盖“首页完全未映射”的 ENOMEM 场景,旧实现 find_area(start_addr) 也能通过,无法防止 [mapped][hole][mapped] 中间空洞误成功的回归。请补一个 3 页匿名映射、munmap 中间页、再对整段 mprotect 期望 ENOMEM 的 normal 回归用例;最好同时验证失败后左右两页仍保持原保护位/可写状态。
本地验证已完成:cargo fmt --check 通过,git diff --check origin/dev...HEAD 通过,cargo xtask clippy --package starry-kernel 通过 13/13。
CI 方面:format、sync-lint、Starry x86_64/loongarch64 QEMU container 均通过;Run clippy / run_host 的日志尾部是 workflow 取消清理,本地 targeted clippy 已通过;Test starry riscv64 qemu / run_host 仍是已知的 smoke 5s 超时,已补充到 #131: https://github.com/rcore-os/tgoskits/issues/131#issuecomment-4626181799。其他 sampled run_host 失败也是后续取消清理,没有看到可归因到本 PR 改动面的独立错误。
重复/重叠检查:base 上已有的 mprotect ENOMEM 测试只覆盖完全未映射首页;open PR #1122 同样涉及 mm hole 的 ENOMEM,但它处理的是 mlock,与本 PR 的 mprotect 语义修复是相邻但不同的 syscall,不是重复实现。当前没有未解决 review thread。
…nmapped hole `sys_mprotect` 只对范围首页做了存在性检查 (`find_area(start_addr)`),范围中 间或末尾的未映射空洞会被静默跳过:形如 `[已映射][空洞][已映射]` 的 mprotect 在 starry 上"成功"返回 0,而 Linux 对范围内任何缺失映射的页都返回 ENOMEM。 改为对整段 `[addr, addr+len)` 做连续覆盖检查。复用已有的 `can_access_range`, 传入空的访问掩码即可得到纯覆盖判定(每个区域的 flags 都包含空集),遇到第一个 空洞即返回 false。检查在修改映射之前进行,失败时不改动任何映射(原子地报 ENOMEM,不留半应用的保护位),这正是依赖该 errno 的程序(GC/sanitizer/W^X JIT 探测自身映射)所需要的行为。 Signed-off-by: Leo Cheng <chengkelfanke@gmail.com>
813b154 to
c7a523f
Compare
There was a problem hiding this comment.
Review: PR #918 — mprotect 空洞连续覆盖检查
改动分析
本 PR 共两个 commit:
- 修复 (
c7a523fa): 将sys_mprotect的存在性检查从find_area(start_addr)(只检查首页)替换为can_access_range(start_addr, length, MappingFlags::empty()),对整个[addr, addr+len)做连续覆盖判定。与 Linuxman 2 mprotect行为对齐:范围内任何缺失映射的页都使调用失败并返回ENOMEM。 - 回归测试 (
5c87d027): 新增测试用例,映射 3 页、munmap 中间页制造[mapped][hole][mapped],再对整段 mprotect 期望 ENOMEM。覆盖了 ZR233 review 中要求的空洞场景回归。
实现逻辑验证
can_access_range(aspace/mod.rs:399)遍历 areas 迭代器,逐区间推进range.start。当area.start() > range.start(有空洞)时返回 false。MappingFlags::empty()是所有 flags 的子集,area.flags().contains(empty())恒为true,因此该调用等价于纯连续覆盖检查。- 检查在
protect()调用之前完成,失败时映射保持不变,满足原子性语义。 - 注释清晰、正确解释了
empty()语义和 ENOMEM 的 POSIX/Linux 来源。
测试覆盖
新测试正确覆盖了「起始页有映射但中段空洞」的回归场景:
mmap3 页 →munmap中间页 →mprotect整段 → 期望ENOMEM- 测试清理了剩余映射(第 1 和第 3 页的 munmap)
base 上已有的 mprotect 未映射区间 → ENOMEM 测试仅覆盖首页完全未映射场景;旧实现 find_area(start) 也能通过。本 PR 补全了该缺失覆盖。
本地验证
cargo fmt --check✅ 通过cargo clippy --package starry-kernel✅ 通过 13/13git diff --check origin/dev...HEAD✅ 无格式问题
CI 状态
CI workflow run #5379(head SHA 5c87d027)刚触发,jobs 尚未开始运行,暂无结论。已通过本地 targeted validation。
重复/重叠分析
- base 上已有
mprotectENOMEM 测试但仅覆盖首页未映射场景。 - open PR #1122(
mlockENOMEM/holes)是同一作者对相邻 syscall 的修复,与本 PR 的mprotect修复是独立的不同 syscall 语义,不是重复实现。 - 其他近期 open PR(#1150 IRQ、#1134 release、#1119 netlink、#1118 shmid 等)均不涉及 mmap/mprotect,无交集。
- 当前无未解决 review thread。
结论
修改逻辑正确、测试覆盖到位、代码清晰、影响范围小。APPROVE。
Powered by mimo-v2.5-pro
| // 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()) { |
There was a problem hiding this comment.
✅ can_access_range + MappingFlags::empty() 是正确的选择:纯连续覆盖检查,contains(empty()) 恒为 true,等价于仅检查区间是否有空洞。逻辑简洁且与 POSIX/Linux 语义一致。
| CHECK_ERR(mprotect(hole, 3 * ps, PROT_READ), | ||
| ENOMEM, "mprotect 跨中段空洞 → ENOMEM"); | ||
| munmap(hole, ps); | ||
| munmap(hole + 2 * ps, ps); |
There was a problem hiding this comment.
✅ 测试结构正确:3 页映射 → munmap 中间页造洞 → mprotect 整段期望 ENOMEM。覆盖了旧实现 find_area(start) 无法检测的 [mapped][hole][mapped] 场景。
ZR233
left a comment
There was a problem hiding this comment.
这轮基于当前 head 5c87d0279020a8cc51f8d9e367b72eb98b99e332 复审后,内核修复方向和本地验证都没有问题,但我这里还是保留一个 blocker:回归测试目前只覆盖了 mprotect([mapped][hole][mapped]) -> ENOMEM,还没有直接证明失败路径上左右两段映射的保护位没有被半应用。
这次我本地重新跑了:
git diff --check origin/dev...HEADcargo fmt --checkcargo xtask clippy --package starry-kernelcargo xtask starry test qemu --arch x86_64 -c syscall
其中 starry-kernel clippy 全部通过,syscall 分组 QEMU 也最终通过并把新的 test-mmap-family case 带进 guest 跑完了,所以实现本身和现有回归都已经有比较强的证据。
但上轮要求的是两件事:
[mapped][hole][mapped]整段mprotect返回ENOMEM;- 失败后左右页的原保护位没有被改掉。
现在 PR 只补了第 1 点。考虑到这次实现是先做整段 coverage pre-check、再调用 protect,我同意实际代码大概率已经满足原子性要求;不过既然 PR body 明确把“失败时不改动映射、不给半应用保护位”作为语义的一部分,normal 回归里最好把这个 ABI 也直接锁住,例如在失败后继续验证左右页仍保持 PROT_READ|PROT_WRITE 的可写行为,或用一个更直接的只读/可写探针把两侧页权限检查出来。
重复/重叠检查:
origin/dev里还没有这个mprotecthole 覆盖修复;- 关键字搜索当前 open PR,没有发现同一语义的重复 PR。
把这条原子性断言补上后,这个 PR 我这里就比较容易放行。
…NOMEM atomically Add a regression to test-mmap-family: map three pages, punch a hole in the middle page, then mprotect the whole [mapped][hole][mapped] range. Linux returns ENOMEM (man 2 mprotect: any unmapped page in the range), whereas the old code only checked the start page's VMA via find_area(start) and silently succeeded across the hole. This exercises the can_access_range contiguous-coverage check. Also lock the atomicity: after the failed mprotect, both surviving pages must stay writable. StarryOS validates the whole range before touching any protection (can_access_range first), so a hole rejects atomically without half-applying; the assertion catches any future move of the pre-check after per-page protect. (Note: real Linux instead applies the prefix up to the hole then returns ENOMEM; StarryOS deliberately picks the safer atomic semantics, documented in the test.) > Hey, we're KunKun Groups, brought to you by heke1228. Quick heads-up: we're > getting renamed to Smart Cluster soon (yeah, you might remember us as Sleepy > Cluster back in the day). Signed-off-by: KunKun Groups <haxxorbunny@qq.com>
5c87d02 to
ec7f7fa
Compare
There was a problem hiding this comment.
Review: PR #918 — mprotect 空洞连续覆盖检查 + 原子性锁
改动分析
本 PR 在当前 head ec7f7fadfda 上共两个 commit:
- 修复 (
c7a523fa): 将sys_mprotect的存在性检查从find_area(start_addr)(只检查首页)替换为can_access_range(start_addr, length, MappingFlags::empty()),对整个[addr, addr+len)做连续覆盖判定。 - 回归测试 + 原子性断言 (
ec7f7fadfda): 新增[mapped][hole][mapped]空洞场景回归,并通过write_faults(SIGSEGV + sigsetjmp) 探针断言失败路径不会半应用保护位。
实现逻辑验证 ✅
can_access_range(aspace/mod.rs:399)遍历 areas 迭代器,逐区间推进range.start。当area.start() > range.start(有空洞)时返回false。MappingFlags::empty()是所有 flags 的子集,area.flags().contains(empty())恒为true,因此该调用等价于纯连续覆盖检查。- 检查在
protect()调用之前完成,失败时映射保持不变,满足原子性语义。 - 注释清晰、正确解释了
empty()语义和 ENOMEM 的 POSIX/Linux 来源。
测试覆盖 ✅
新测试完整覆盖了 ZR233 此前两轮 review 的全部要求:
- 空洞 ENOMEM: 3 页映射 → munmap 中间页造洞 → mprotect 整段 → 期望
ENOMEM✅ - 原子性断言: 失败后通过
write_faults(hole)和write_faults(hole + 2*ps)验证左右两页仍保持PROT_READ|PROT_WRITE的可写状态 ✅
write_faults 使用 SIGSEGV + sigsetjmp 实现轻量写探针,能精确检测页面是否可写。测试注释也正确文档化了 StarryOS 原子语义与 Linux 前缀应用语义的区别。
本地验证
cargo fmt --check✅ 通过cargo xtask clippy --package starry-kernel✅ 通过 13/13git diff --check origin/dev...HEAD✅ 无格式问题cargo xtask starry test qemu --arch x86_64 -c syscall✅ syscall 分组全部通过(含 test-mmap-family),耗时 173s
CI 状态
当前 head SHA ec7f7fadfda 的 CI workflow run #5412 状态为 pending(刚触发),尚无 job 完成。前序 head 5c87d027 的 format、sync-lint、Starry x86_64/loongarch64 QEMU container 均已通过;当前 head 仅新增了测试代码(无内核代码变更),本地 QEMU 已验证通过。
重复/重叠分析
- base 上已有
mprotectENOMEM 测试但仅覆盖首页未映射场景。 - open PR #1122(
mlockENOMEM/holes)是同一作者对相邻 syscall 的修复,与本 PR 的mprotect修复是独立的不同 syscall 语义,不是重复实现。 - 其他近期 open PR(#1156 cgroup、#1155 IRQ、#1151 release 等)均不涉及 mmap/mprotect,无交集。
- 当前无未解决 review thread。
结论
修改逻辑正确、测试覆盖到位(含原子性断言)、代码清晰、影响范围小。所有本地验证(含 QEMU)通过。
APPROVE。
Powered by mimo-v2.5-pro
| /* 在中间页打一个洞, 留下 [mapped][hole][mapped] */ | ||
| CHECK_RET(munmap(hole + ps, ps), 0, "munmap 中间页造洞"); | ||
| CHECK_ERR(mprotect(hole, 3 * ps, PROT_READ), | ||
| ENOMEM, "mprotect 跨中段空洞 → ENOMEM"); |
There was a problem hiding this comment.
✅ write_faults + SIGSEGV/sigsetjmp 探针方案正确且轻量:在 mprotect 失败后直接写一字节到左右页,若页面仍可写则不触发 SIGSEGV(g_faulted == 0),否则通过 siglongjmp 安全恢复。这精确锁住了 ZR233 要求的原子性 ABI。
注释也正确文档化了 StarryOS 的原子语义(先检查再改,全有或全无)与 Linux 的差异(先改前缀再返回 ENOMEM),避免后续维护者误以为这是 bug。
| // 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()) { |
There was a problem hiding this comment.
✅ can_access_range(start_addr, length, MappingFlags::empty()) 逻辑正确:纯连续覆盖检查,contains(empty()) 恒为 true,遇到空洞即返回 false。检查在 protect() 前完成,失败路径映射不变。
There was a problem hiding this comment.
审阅: PR #918 — mprotect 空洞连续覆盖检查 + 原子性断言
改动分析
本 PR 两个 commit:
- 内核修复 (
c7a523fa): 将sys_mprotect的存在性检查从find_area(start_addr)(仅检查首页)替换为can_access_range(start_addr, length, MappingFlags::empty()),对整个[addr, addr+len)做连续覆盖判定,与 Linuxman 2 mprotect对区间内任意未映射页返回ENOMEM的语义一致。 - 回归测试 + 原子性断言 (
ec7f7fadfda): 新增[mapped][hole][mapped]空洞 ENOMEM 回归,并通过write_faults(SIGSEGV + sigsetjmp)探针断言失败路径左右两页仍保持PROT_READ|PROT_WRITE可写状态。
逻辑验证 ✅
can_access_range(aspace/mod.rs:399)遍历 areas 迭代器,逐区间推进range.start;当area.start() > range.start(有空洞)或遍历结束后范围未覆盖完毕时返回false。MappingFlags::empty()是所有 flags 的子集,area.flags().contains(empty())恒为true,等价于纯连续覆盖检查。- 检查在
protect()调用之前完成,失败时映射保持不变,满足原子性语义。 - 注释清晰,正确解释了
empty()语义和 ENOMEM 的 POSIX/Linux 来源,以及 StarryOS 原子语义与 Linux 前缀应用语义的区别。
测试覆盖 ✅
write_faults使用 SIGSEGV + sigsetjmp 实现轻量写探针,精确检测页面是否可写。- 测试完整覆盖了 ZR233 前两轮 review 的全部要求:
- 3 页映射 → munmap 中间页造洞 → mprotect 整段 → ENOMEM ✅
- 失败后通过
write_faults(hole)和write_faults(hole + 2*ps)验证左右两页仍保持原 RW 可写状态 ✅
- base 上已有的 mprotect ENOMEM 测试仅覆盖首页完全未映射场景,旧实现
find_area(start)也能通过。本 PR 补全了缺失覆盖。
前序 review 意见处理
- ZR233 第一轮(
813b1545)CHANGES_REQUESTED:要求补[mapped][hole][mapped]ENOMEM 回归 → 已在5c87d027补充 ✅ - ZR233 第二轮(
5c87d027)CHANGES_REQUESTED:要求补原子性断言(失败后左右页保护位不变)→ 已在ec7f7fadfda通过write_faults探针锁住 ✅ - 当前无未解决 review thread。
本地验证
cargo fmt --check✅ 通过cargo clippy --manifest-path os/StarryOS/kernel/Cargo.toml --all-features -- -D warnings✅ 通过git diff --check✅ 无格式问题
CI 状态
当前 head SHA ec7f7fadfda 的 CI workflow run #5412 状态为 pending(刚触发)。前序 commit 的 format、sync-lint、QEMU container 测试均已通过。本地 targeted clippy 已验证通过。无可归因到本 PR 改动面的 CI 失败。
重复/重叠分析
- PR #285(已合并)引入了
mprotectENOMEM 检查但使用find_area(start)只检查首页,本 PR 是其正确性的后续改进。 - open PR #1122(
mlockENOMEM/holes)同一作者对相邻 syscall 的修复,与本 PR 是独立的不同 syscall 语义,非重复实现。 - 其他近期 open PR 均不涉及 mmap/mprotect,无交集。
结论
修改逻辑正确、测试覆盖到位(含原子性断言)、代码清晰、影响范围小。所有本地验证通过,前序 review 意见已全部解决。
APPROVE。
Powered by mimo-v2.5-pro
ZR233
left a comment
There was a problem hiding this comment.
复查当前 head ec7f7fadfda,前两轮阻塞项已经解决,我这里批准。
检查点:
sys_mprotect从find_area(start_addr)改为can_access_range(start_addr, length, MappingFlags::empty()),覆盖检查从“只看起始页”提升为整段连续覆盖;MappingFlags::empty()在这里等价于纯 coverage check,遇到中间/尾部 hole 会返回false。- 检查发生在
protect()之前,失败路径不会半应用新的保护位,符合 PR 中声明的 StarryOS 原子拒绝语义。 test-mmap-family现在覆盖 3 页映射、中间页munmap、整段mprotect返回ENOMEM,并通过write_faults验证失败后左右页仍保持可写,补齐了此前要求的原子性断言。- 我前两轮留下的两个 review thread 已经标记为 resolved。
本地验证:
cargo fmt --check通过。git diff --check origin/dev...HEAD通过。cargo xtask clippy --package starry-kernel通过 13/13。cargo xtask starry test qemu --arch x86_64 --test-group normal -c syscall的 xtask 汇总为 PASS;同时我临时收窄到test-mmap-family复核新增 mprotect 断言,mprotect 跨中段空洞 -> ENOMEM、失败后左页仍可写、失败后右页仍可写三项均 PASS。该二进制在本机仍暴露一个既有的mmap PRIVATE|SHARED断言失败,和本 PR 新增/修改的 mprotect 路径无关,建议后续单独跟踪。
CI:当前 head 的 GitHub checks 已通过。重叠检查:#1122 处理 mlock hole/ENOMEM,#1120 处理 mmap overflow,#1114 处理 madvise/基础兼容修复,均不是本 PR 的重复实现。
- mount.rs: keep Arc children model with clone_tree/clone_children_from/clone_shallow - dir.rs: adopt upstream generation-based cache + mountpoint rename transfer - inode.rs: merge upstream (name, is_dir) signature with pending-delete + truncate_inode Upstream changes absorbed: - fix(axfs-ng): zero partial last page when truncating (rcore-os#1124) - fix(locking): narrow spinlock scope in VFS and Starry paths (rcore-os#1146) - chore(axbuild): pin ostool runtime bin fix (rcore-os#1158) - feat(starry-apps): runnable eBPF demos (rcore-os#1132) - feat(starry): wire qperf app runtime into Starry perf (rcore-os#1095) - fix(axcpu-aarch64): emulate EL0 MRS reads of ID_AA64* (rcore-os#1128) - feat(rsext4): fine-grained locking for SMP (rcore-os#1057) - feat(starry-kernel): implement TCP_INFO sockopt (rcore-os#1044) - test(starryos): add fork parent-child identity test (rcore-os#995) - fix(starry-mm): mprotect returns ENOMEM on unmapped holes (rcore-os#918) Tests verified: - test-nix-prereqs: PASS - nix-smoke: PASS - syscall (upstream): PASS - test-dup2 (upstream): PASS
…range (rcore-os#918) * fix(starry-mm): return ENOMEM from mprotect when the range spans an unmapped hole `sys_mprotect` 只对范围首页做了存在性检查 (`find_area(start_addr)`),范围中 间或末尾的未映射空洞会被静默跳过:形如 `[已映射][空洞][已映射]` 的 mprotect 在 starry 上"成功"返回 0,而 Linux 对范围内任何缺失映射的页都返回 ENOMEM。 改为对整段 `[addr, addr+len)` 做连续覆盖检查。复用已有的 `can_access_range`, 传入空的访问掩码即可得到纯覆盖判定(每个区域的 flags 都包含空集),遇到第一个 空洞即返回 false。检查在修改映射之前进行,失败时不改动任何映射(原子地报 ENOMEM,不留半应用的保护位),这正是依赖该 errno 的程序(GC/sanitizer/W^X JIT 探测自身映射)所需要的行为。 Signed-off-by: Leo Cheng <chengkelfanke@gmail.com> * test(starry-mm): cover mprotect over [mapped][hole][mapped] returns ENOMEM atomically Add a regression to test-mmap-family: map three pages, punch a hole in the middle page, then mprotect the whole [mapped][hole][mapped] range. Linux returns ENOMEM (man 2 mprotect: any unmapped page in the range), whereas the old code only checked the start page's VMA via find_area(start) and silently succeeded across the hole. This exercises the can_access_range contiguous-coverage check. Also lock the atomicity: after the failed mprotect, both surviving pages must stay writable. StarryOS validates the whole range before touching any protection (can_access_range first), so a hole rejects atomically without half-applying; the assertion catches any future move of the pre-check after per-page protect. (Note: real Linux instead applies the prefix up to the hole then returns ENOMEM; StarryOS deliberately picks the safer atomic semantics, documented in the test.) > Hey, we're KunKun Groups, brought to you by heke1228. Quick heads-up: we're > getting renamed to Smart Cluster soon (yeah, you might remember us as Sleepy > Cluster back in the day). Signed-off-by: KunKun Groups <haxxorbunny@qq.com> --------- Signed-off-by: Leo Cheng <chengkelfanke@gmail.com> Signed-off-by: KunKun Groups <haxxorbunny@qq.com> Co-authored-by: KunKun Groups <haxxorbunny@qq.com>
…range (#918) * fix(starry-mm): return ENOMEM from mprotect when the range spans an unmapped hole `sys_mprotect` 只对范围首页做了存在性检查 (`find_area(start_addr)`),范围中 间或末尾的未映射空洞会被静默跳过:形如 `[已映射][空洞][已映射]` 的 mprotect 在 starry 上"成功"返回 0,而 Linux 对范围内任何缺失映射的页都返回 ENOMEM。 改为对整段 `[addr, addr+len)` 做连续覆盖检查。复用已有的 `can_access_range`, 传入空的访问掩码即可得到纯覆盖判定(每个区域的 flags 都包含空集),遇到第一个 空洞即返回 false。检查在修改映射之前进行,失败时不改动任何映射(原子地报 ENOMEM,不留半应用的保护位),这正是依赖该 errno 的程序(GC/sanitizer/W^X JIT 探测自身映射)所需要的行为。 Signed-off-by: Leo Cheng <chengkelfanke@gmail.com> * test(starry-mm): cover mprotect over [mapped][hole][mapped] returns ENOMEM atomically Add a regression to test-mmap-family: map three pages, punch a hole in the middle page, then mprotect the whole [mapped][hole][mapped] range. Linux returns ENOMEM (man 2 mprotect: any unmapped page in the range), whereas the old code only checked the start page's VMA via find_area(start) and silently succeeded across the hole. This exercises the can_access_range contiguous-coverage check. Also lock the atomicity: after the failed mprotect, both surviving pages must stay writable. StarryOS validates the whole range before touching any protection (can_access_range first), so a hole rejects atomically without half-applying; the assertion catches any future move of the pre-check after per-page protect. (Note: real Linux instead applies the prefix up to the hole then returns ENOMEM; StarryOS deliberately picks the safer atomic semantics, documented in the test.) > Hey, we're KunKun Groups, brought to you by heke1228. Quick heads-up: we're > getting renamed to Smart Cluster soon (yeah, you might remember us as Sleepy > Cluster back in the day). Signed-off-by: KunKun Groups <haxxorbunny@qq.com> --------- Signed-off-by: Leo Cheng <chengkelfanke@gmail.com> Signed-off-by: KunKun Groups <haxxorbunny@qq.com> Co-authored-by: KunKun Groups <haxxorbunny@qq.com>
这个改动做了什么
sys_mprotect之前只对范围首页做存在性检查(find_area(start_addr)),范围中间或末尾的未映射空洞会被静默跳过——形如[已映射][空洞][已映射]的mprotect(addr, len)在 StarryOS 上会"成功"返回 0。为什么要改
man 2 mprotect规定:范围内任何缺失映射的页都使调用失败并返回ENOMEM(保护位应用到空洞之前的前缀)。静默成功会让自己探测映射、依赖该errno的程序(GC、sanitizer、做 W^X 的 JIT 等)读到错误结果。怎么改的
对整段
[addr, addr+len)做连续覆盖检查:复用已有的can_access_range,传入空访问掩码即得到纯覆盖判定(每个区域的 flags 都包含空集),遇到第一个空洞即返回 false。检查在修改任何映射之前完成,失败时不改动映射,原子地报ENOMEM,不留半应用的保护位。验证
STARRY_GROUPED_TESTS_PASSED,本改动未引入新失败。