Skip to content

linux: support protection keys for Chromium sandboxing - #522

Merged
laffer1 merged 5 commits into
masterfrom
linux-pkey-backport
Sep 25, 2026
Merged

laffer1 merged 5 commits into
masterfrom
linux-pkey-backport

Conversation

@laffer1

@laffer1 laffer1 commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

Summary

  • implement Linux pkey_alloc, pkey_free, and pkey_mprotect
  • bridge Linux protection keys to native amd64 PKU support
  • add XSAVE component layout query helpers required to update PKRU state
  • inherit protection-key allocation state across fork and reset it on exec
  • initialize Linux PKRU state for Chromium/V8 sandbox expectations
  • provide Linux-compatible no-PKU stubs on arm64 and i386
  • document the backport plan and add an UPDATING entry

Validation

  • make -j2 buildkernel KERNCONF=GENERIC
  • built linux_common.ko, linux64.ko, and linux.ko
  • rebuilt fpu.o and sys_machdep.o with -Werror
  • verified pkey/PKRU symbols in linux_common.ko
  • git diff --check

The repository C precommit script was also run. cppcheck reported parser failures in existing IFUNC and macro constructs rather than diagnostics in the new code. Splint skipped the kernel-only C sources by design.

Runtime Brave validation requires installing this kernel and rebooting.

AI-Assisted-by: OpenAI Codex (GPT-5)
Obtained from: FreeBSD commits 7bcaff05223e, b9951017bab3, and bdb561843e86
Tested by: Lucas Holt luke@foolishgames.com

Summary by Sourcery

Add Linux protection-key support backed by native amd64 PKU to enable Chromium and Brave sandboxing.

New Features:

  • Add Linux protection-key syscalls for allocating, freeing, and applying memory protection keys.
  • Enable Chromium-compatible PKU state initialization and protection-key lifecycle handling on amd64.
  • Provide Linux-compatible fallback behavior on arm64 and i386 systems without PKU support.

Enhancements:

  • Expose XSAVE feature and layout queries needed to manage PKRU state.
  • Preserve protection-key allocation state across fork and reset it during exec.

Build:

  • Update Linux module build configuration to include the new architecture-specific protection-key support.

Documentation:

  • Document the Linux Brave sandbox backport plan and related follow-up work.
  • Record the change in UPDATING.

Backport Linuxulator pkey_alloc, pkey_free, and pkey_mprotect support for Chromium V8 sandboxing. Add the required XSAVE layout helpers and Linux-compatible PKRU process lifecycle handling.

AI-Assisted-by: OpenAI Codex (GPT-5)
Signed-off-by: Lucas Holt <luke@foolishgames.com>
AI-Assisted-by: OpenAI Codex (GPT-5)
Signed-off-by: Lucas Holt <luke@foolishgames.com>
@sourcery-ai

sourcery-ai Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

This backport enables Linux protection-key syscalls for Chromium/V8 sandboxing by combining common Linux ABI validation, amd64 native PKU and XSAVE state handling, per-process allocation lifecycle management, and unsupported-architecture stubs, with corresponding module and documentation updates.

Sequence diagram for Linux protection-key allocation and memory tagging

sequenceDiagram
    participant App as Linux application
    participant ABI as Linux pkey syscall ABI
    participant Common as linux_pkey_*_common
    participant PKU as amd64 PKU backend
    participant VM as amd64_pkru_update

    App->>ABI: pkey_alloc(flags, init_val)
    ABI->>Common: linux_pkey_alloc_common(flags, init_val)
    Common->>PKU: linux_pkey_alloc_machdep(td, init_val)
    PKU-->>Common: allocated key
    Common-->>App: pkey

    App->>ABI: pkey_mprotect(addr, len, prot, pkey)
    ABI->>Common: linux_pkey_mprotect_common(addr, len, prot, pkey)
    Common->>PKU: linux_pkey_mprotect_machdep(td, addr, len, prot, pkey)
    PKU->>VM: amd64_pkru_update(td, addr, len, pkey, flags, clear)
    VM-->>App: result
Loading

State diagram for Linux protection-key lifecycle

stateDiagram-v2
    [*] --> LinuxProcess
    LinuxProcess --> LinuxProcess: linux_pemuldata_init_md()
    LinuxProcess --> ForkedProcess: fork inherits md_pkey_allocation_map
    ForkedProcess --> ForkedProcess: pkey_alloc / pkey_free
    LinuxProcess --> ExecedProcess: exec
    ExecedProcess --> ExecedProcess: linux_pemuldata_exec_md()
    ExecedProcess --> PKRUInitialized: linux_pkru_exec_init()
    PKRUInitialized --> LinuxProcess: allocation map = LINUX_PKEY_INITIAL_MAP
Loading

File-Level Changes

Change Details Files
Added Linux protection-key syscall plumbing with shared validation and architecture-specific implementations.
  • Replaced ENOSYS syscall stubs for pkey_alloc, pkey_free, and pkey_mprotect.
  • Validated flags, access rights, key ranges, and the pkey -1 fallback in common Linux mmap code.
  • Implemented native amd64 PKU allocation, page tagging, and PKRU permission updates.
  • Added no-PKU ENOSPC/EINVAL behavior for arm64 and i386.
sys/compat/linux/linux_dummy.c
sys/compat/linux/linux_mmap.c
sys/compat/linux/linux_mmap.h
sys/amd64/linux/linux_machdep.c
sys/amd64/linux32/linux32_machdep.c
sys/amd64/linux/linux_pkru.c
sys/arm64/linux/linux_emul_md.c
sys/i386/linux/linux_emul_md.c
Extended Linux process lifecycle handling for protection-key state and PKRU initialization.
  • Stored amd64 key allocation state in per-process Linux emulation data.
  • Inherited allocation state during fork and reset it during exec.
  • Initialized exec PKRU to Linux's 0x55555554 default for both 64-bit and 32-bit Linux ABIs.
  • Added architecture-specific emulation-data headers and lifecycle hooks.
sys/compat/linux/linux_emul.c
sys/compat/linux/linux_emul.h
sys/amd64/linux/linux_emul_md.h
sys/amd64/linux/linux_sysvec.c
sys/amd64/linux32/linux32_sysvec.c
sys/arm64/linux/linux_emul_md.h
sys/i386/linux/linux_emul_md.h
Exposed XSAVE metadata and added amd64 memory-map PKRU update support.
  • Tracked supervisor-state masks, XSAVE extensions, component flags, and component layouts from CPUID.
  • Added helpers for feature support, compact/non-compact offsets, sizes, and header location.
  • Applied PKRU changes to validated virtual-memory ranges through the amd64 pmap layer.
sys/amd64/amd64/fpu.c
sys/amd64/amd64/sys_machdep.c
sys/x86/include/fpu.h
sys/x86/include/specialreg.h
sys/x86/include/sysarch.h
Updated module integration and documented the upstream backport.
  • Included machine-dependent Linux sources in the Linux and linux_common module builds.
  • Documented upstream commit provenance, integration decisions, validation plan, and follow-up scope.
  • Added an UPDATING entry.
sys/modules/linux/Makefile
sys/modules/linux_common/Makefile
LINUX_BRAVE_SANDBOX_BACKPORT.md
UPDATING

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="sys/amd64/linux/linux_pkru.c" line_range="185-187" />
<code_context>
+
+	pem = pem_find(td->td_proc);
+	LINUX_PEM_XLOCK(pem);
+	if ((pem->pem_md.md_pkey_allocation_map & (1u << pkey)) == 0) {
+		LINUX_PEM_XUNLOCK(pem);
+		return (EINVAL);
+	}
+	pem->pem_md.md_pkey_allocation_map &= ~(1u << pkey);
</code_context>
<issue_to_address>
**issue (bug_risk):** `pkey_free(0)` succeeds and removes key 0 from the allocation map, even though Linux reserves key 0 and rejects attempts to free it with `EINVAL`. Subsequent `pkey_mprotect(..., 0)` calls then incorrectly fail because the default key is no longer marked allocated.

**Triggers:** When a Linux application calls `pkey_free(0)`.

**Suggested fix:** Reject pkey 0 explicitly before modifying the allocation map.
</issue_to_address>

### Comment 2
<location path="sys/amd64/linux/linux_pkru.c" line_range="217-226" />
<code_context>
+	}
+	LINUX_PEM_SUNLOCK(pem);
+
+	error = linux_mprotect_common(td, addr, len, prot);
+	if (error != 0 || len == 0)
+		return (error);
+
+	/*
+	 * Tag the range; a pkey of 0 untags it.  The tag is not
+	 * persistent: it dies with the mapping, matching Linux VMA
+	 * semantics.
+	 */
+	return (amd64_pkru_update(td, addr, len, pkey, 0, pkey == 0));
+}
</code_context>
<issue_to_address>
**issue (broader_impact):** `linux_pkey_mprotect_machdep` applies the ordinary memory protections before assigning the protection-key tag. If `amd64_pkru_update` fails, the syscall returns an error while leaving the mapping's protections changed and its protection-key tag unchanged, producing a partial update.

**Triggers:** When `linux_mprotect_common` succeeds but `amd64_pkru_update` fails, such as from a pmap allocation failure.

**Suggested fix:** Arrange for validation and pkey tagging to succeed before committing the protection change, or roll back the ordinary protection change when the pkey update fails.
</issue_to_address>

### Comment 3
<location path="sys/amd64/linux/linux_emul_md.h" line_range="26-27" />
<code_context>
+#define LINUX_PKEY_INITIAL_MAP 0x1
+
+/*
+ * Initial PKRU at exec: access disabled for keys 1..15, key 0 open;
+ * the Linux init_pkru default.
+ */
+#define LINUX_PKRU_INIT 0x55555554
+
+struct thread;
</code_context>
<issue_to_address>
**nitpick:** The comments describe `0x55555554` as disabling access for keys 1 through 15, but that value sets the write-disable bit for those keys and leaves their access-disable bits clear. The in-code explanation therefore misstates the actual PKRU behavior and can mislead future changes to the sandbox initialization.

**Suggested fix:** Describe the value as disabling writes while allowing reads, or use the actual access-disable encoding if full access denial is intended.

```suggestion
 * Initial PKRU at exec: writes disabled for keys 1..15, reads allowed;
 * the Linux init_pkru default.
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread sys/amd64/linux/linux_pkru.c
Comment thread sys/amd64/linux/linux_pkru.c
Comment thread sys/amd64/linux/linux_emul_md.h
@laffer1

laffer1 commented Sep 25, 2026

Copy link
Copy Markdown
Member Author

@claude review this

@github-actions

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Claude encountered an error after 2s —— View job


I'll analyze this and get back to you.

@laffer1

laffer1 commented Sep 25, 2026

Copy link
Copy Markdown
Member Author

@claude try to review again

*/

int
linux_pkey_alloc_machdep(struct thread *td, uint64_t init_val)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

High: linux64.ko on arm64 will have undefined symbols. This PR removes the DUMMY(pkey_mprotect/pkey_alloc/pkey_free) stubs from sys/compat/linux/linux_dummy.c (compiled into linux_common.ko), and sys/arm64/linux/linux_sysent.c references linux_pkey_mprotect, linux_pkey_alloc and linux_pkey_free (syscalls 288-290). The syscall entry points were only added to sys/amd64/linux/linux_machdep.c and sys/amd64/linux32/linux32_machdep.c; nothing on arm64 defines them, so the module fails to link/load. Upstream FreeBSD put the wrappers in the shared sys/compat/linux/linux_misc.c for this reason. Fix: add the three wrappers to sys/arm64/linux/linux_machdep.c (next to linux_mprotect), or move them to linux_misc.c.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed by moving the three pkey syscall wrappers into shared sys/compat/linux/linux_misc.c, matching the current FreeBSD layout. This gives arm64 linux64.ko definitions for linux_pkey_mprotect, linux_pkey_alloc, and linux_pkey_free after the dummy stubs are removed.

*/

int
linux_pkey_alloc_machdep(struct thread *td, uint64_t init_val)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

High: Same problem as arm64: sys/i386/linux/linux_sysent.c references linux_pkey_mprotect/linux_pkey_alloc/linux_pkey_free (syscalls 380-382), the DUMMY() definitions were removed from linux_dummy.c (compiled into linux.ko on i386), and no i386 file defines the wrappers. linux.ko on i386 will have unresolved symbols and fail to load. Add the wrappers to sys/i386/linux/linux_machdep.c next to linux_mprotect.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed by moving the three wrappers into shared sys/compat/linux/linux_misc.c and removing the amd64/amd64-linux32 duplicates. The shared source is built by native i386 linux.ko, so the generated i386 syscall entries now have definitions. A clean amd64 GENERIC build also confirmed that both linux.ko and linux64.ko export all three symbols.

Comment thread sys/amd64/linux/linux_pkru.c Outdated
linux_pkey_supported(void)
{

return ((cpu_stdext_feature2 & CPUID_STDEXT2_OSPKE) != 0);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium: linux_pkey_supported() only checks CPUID_STDEXT2_OSPKE, which can be stale. identify_cpu_ext_features() re-reads leaf 7 in initializecpu() right after CR4.PKE is set, so OSPKE is latched as 1. Later, fpuinit_bsp1() (sys/amd64/amd64/fpu.c ~line 403) honours the hw.xsave_mask tunable: if PKRU saving was disabled it clears CR4.PKE and CPUID_STDEXT2_PKU, but leaves OSPKE set. On such a boot pkey_alloc() still reports support and calls linux_pkru_write(): the fast path executes wrpkru with CR4.PKE clear (#UD -> kernel panic), and the slow path calls xsave_area_offset(xsave_mask, XFEATURE_ENABLED_PKRU, ...) with PKRU absent from xsave_mask, which trips the KASSERT (or reads xsave_area_desc out of bounds on non-INVARIANTS kernels, since the array is sized by flsl(xsave_mask | xsave_mask_supervisor)). Suggest also requiring CPUID_STDEXT2_PKU and (xsave_mask & XFEATURE_ENABLED_PKRU) != 0 (i.e. the same condition pmap_pkru_check_uargs and the fpu code use), so the Linux side degrades to the no-PKU stubs instead.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed. linux_pkey_supported() now requires both CPUID_STDEXT2_PKU and CPUID_STDEXT2_OSPKE, plus XFEATURE_ENABLED_PKRU in xsave_mask. If the tunable disables PKRU state saving, the Linux pkey paths now degrade to their unsupported return values without executing WRPKRU or looking up a missing XSAVE component. A clean GENERIC kernel and module build completed successfully.

@github-actions

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Claude finished @laffer1's task in 3m 43s —— View job


Review of PR #522 (re-review)

  • Gather context (diff, changed files)
  • Review amd64 pkey/PKRU implementation
  • Review common Linux mmap/emul plumbing
  • Review arm64/i386 stubs
  • Check style(9) / malloc-free / bounds issues
  • Post final review

Blocking: i386 and arm64 Linux modules will fail to build/load

sys/compat/linux/linux_dummy.c drops the DUMMY(pkey_alloc), DUMMY(pkey_free), DUMMY(pkey_mprotect) stubs, which previously supplied the linux_pkey_alloc/linux_pkey_free/linux_pkey_mprotect symbols on every architecture. Real replacements were only added for amd64:

  • sys/amd64/linux/linux_machdep.c and sys/amd64/linux32/linux32_machdep.c gained the syscall entry points (linux_pkey_alloc, linux_pkey_free, linux_pkey_mprotect) that call into the new *_common/*_machdep helpers.
  • sys/i386/linux/linux_machdep.c and sys/arm64/linux/linux_machdep.c were not touched — only the lower-level linux_pkey_*_machdep/linux_pemuldata_*_md backends were added via the new linux_emul_md.c files.

But syscalls.master for both archs declares these as STD syscalls, and the checked-in generated tables reference the symbols directly:

sys/i386/linux/linux_sysent.c:399: .sy_call = (sy_call_t *)linux_pkey_mprotect, /* 380 */
sys/i386/linux/linux_sysent.c:400: .sy_call = (sy_call_t *)linux_pkey_alloc,    /* 381 */
sys/i386/linux/linux_sysent.c:401: .sy_call = (sy_call_t *)linux_pkey_free,     /* 382 */
sys/arm64/linux/linux_sysent.c:307-309: same, at 288/289/290

With the dummy stubs gone and no arch-specific replacement, linux_pkey_alloc/linux_pkey_free/linux_pkey_mprotect are undefined on i386 and arm64 — the modules referencing them can't link/load.

It's worse than just the pkey syscalls on i386: sys/compat/linux/linux_emul.c's linux_proc_init() now unconditionally calls linux_pemuldata_init_md()/linux_pemuldata_exec_md(). sys/modules/linux_common/Makefile adds linux_emul_md.c to the build for aarch64 (and folds the amd64 equivalent into linux_pkru.c), but there's no matching .if ${MACHINE_CPUARCH} == "i386" block, so linux_common.ko on i386 won't even define those two symbols within itself — and linux_common.ko is the dependency, loaded before linux.ko (which is where sys/i386/linux/linux_emul_md.c currently ends up), so the symbols can't be resolved at load time either way. This breaks Linuxulator loading on i386 entirely, not just pkey_*.

Fix suggestions:

  1. Add linux_pkey_alloc/linux_pkey_free/linux_pkey_mprotect wrappers to sys/i386/linux/linux_machdep.c and sys/arm64/linux/linux_machdep.c, mirroring the amd64/linux32 versions (calling linux_pkey_alloc_common/etc.).
  2. Add an .if ${MACHINE_CPUARCH} == "i386" block to sys/modules/linux_common/Makefile that adds .PATH sys/i386/linux and SRCS+= linux_emul_md.c, matching the aarch64 block already present.
  3. Cross-build (or at least compile-check) linux.ko/linux64.ko for i386 and arm64 before merging — the PR's validation notes only cover an amd64 buildkernel.

Other notes

The three issues Sourcery raised earlier (pkey 0 free semantics, protection-vs-tagging ordering in linux_pkey_mprotect_machdep, and the LINUX_PKRU_INIT comment) were already discussed inline and correctly rebutted by @laffer1 — those all match real Linux/PKRU semantics, no changes needed there.

No malloc/free asymmetries, bounds-check gaps, or style(9) issues found in the amd64-specific code (linux_pkru.c, fpu.c, sys_machdep.c) — locking around pem_md.md_pkey_allocation_map is consistent, and amd64_pkru_update()'s use of vm_map_check_boundary() matches existing patterns in the tree.

Define the pkey syscall wrappers in shared Linux compatibility code so arm64 and i386 modules resolve their syscall entries. Require active PKU, OSPKE, and PKRU XSAVE state before accessing PKRU.

AI-Assisted-by: OpenAI Codex (GPT-5)
Signed-off-by: Lucas Holt <luke@foolishgames.com>
@github-actions

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Claude finished @laffer1's task in 2m 46s —— View job


Reviewing PR #522 (re-review requested)

  • Gather context (diff vs origin/master, changed files)
  • Review shared syscall wrappers moved to linux_misc.c
  • Review amd64 pkey/PKRU implementation (linux_pkru.c, fpu.c, sys_machdep.c)
  • Review arm64/i386 no-PKU stubs
  • Review module Makefiles (linux, linux_common)
  • Check style(9) / malloc-free / bounds issues
  • Post final review

Re-review of the latest commit (2c13119)

Both blocking issues from the previous round are fixed:

  1. i386/arm64 undefined symbols — linux_pkey_alloc/_free/_mprotect wrappers now live in shared sys/compat/linux/linux_misc.c:352-372, calling into linux_pkey_*_common (sys/compat/linux/linux_mmap.c:246-280). Every ABI-specific module (linux64.ko, linux.ko) compiles linux_misc.c itself, so all three symbols are defined on every arch. sys/i386/linux/linux_emul_md.c and sys/arm64/linux/linux_emul_md.c supply the no-PKU EINVAL/ENOSPC back ends and empty linux_pemuldata_*_md hooks, and both are wired into their respective builds (sys/modules/linux/Makefile:69 already had linux_emul_md.c for i386; sys/modules/linux_common/Makefile:5-7,15-16 adds the aarch64 .PATH/SRCS block). Verified EXPORT_SYMS=YES in linux_common/Makefile exports these to dependent linux64.ko/linux.ko, so the amd64 pkey backend (linux_pkru.c, now built into linux_common.ko per lines 5-6/14-15) is also visible.
  2. Stale OSPKE-only feature check — linux_pkey_supported() in sys/amd64/linux/linux_pkru.c:38-44 now requires CPUID_STDEXT2_PKU, CPUID_STDEXT2_OSPKE, and (xsave_mask & XFEATURE_ENABLED_PKRU) != 0, matching the guard used by pmap_pkru_check_uargs/the FPU code, so a hw.xsave_mask tunable that strips PKRU no longer reaches WRPKRU/xsave_area_offset() with a missing component.

Also re-checked sys/amd64/amd64/fpu.c:451: max_ext_n = flsl(xsave_mask | xsave_mask_supervisor) now correctly sizes xsave_area_desc[] to cover supervisor-state components (the old flsl(xsave_mask) would have under-allocated if a supervisor feature had a higher bit index than any user feature) — no OOB write into the new .flags field.

Nothing new to add beyond what Sourcery/laffer1 already settled inline (pkey-0 free semantics, mprotect/pkru-tag ordering, and the LINUX_PKRU_INIT comment — laffer1's rebuttals for all three match real Linux PKRU/AD-bit semantics).

Minor style nit (non-blocking): the new header guards use a space instead of the tree's usual tab after #define, e.g. sys/amd64/linux/linux_emul_md.h:8, sys/arm64/linux/linux_emul_md.h:8, sys/i386/linux/linux_emul_md.h:8 (#define _AMD64_LINUX_EMUL_MD_H_ vs. the surrounding tree's #define\t_LINUX_EMUL_H_ style, e.g. sys/compat/linux/linux_emul.h:33).

No malloc/free asymmetries, missing bounds checks, or other style(9) violations found; no assembly or contrib/mksh files touched. I was not able to run tools/build/checkstyle9.pl or a cross-build for i386/arm64 in this sandbox (permission-restricted), so a native/cross build check for those two archs before merge is still worth doing, per the PR's own validation notes (only amd64 buildkernel was run).

}
LINUX_PEM_SUNLOCK(pem);

error = linux_mprotect_common(td, addr, len, prot);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[low] pkey_mprotect is not atomic here: linux_mprotect_common() changes the page protections first, and only afterwards does amd64_pkru_update() apply the key tag. If the tagging step fails (e.g. vm_map_check_boundary() rejecting a range that straddles a fixed-boundary entry, or pmap_pkru_set() returning an error), the syscall reports failure to the caller while the new prot has already been applied. On Linux do_mprotect_pkey() performs both under one mmap write lock and either both happen or neither. Callers that treat a failed pkey_mprotect() as "nothing changed" (Chromium checks the return value and falls back to plain mprotect semantics) can end up with a range whose protection changed but which carries the old key. Consider validating the range (vm_map_check_boundary on the same page-rounded bounds) before calling linux_mprotect_common(), or reverting the protection change when tagging fails.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed. The fixed-boundary rejection can occur after linux_mprotect_common() has already changed protections. I have added a preflight check under the VM map read lock using the same vm_map_check_boundary() predicate before calling linux_mprotect_common(). amd64_pkru_update() retains its second locked validation to synchronize the actual tag update with concurrent map changes. This avoids the deterministic partial-update case without attempting an unsafe rollback across mappings that may have different prior protections. The remaining PKRU checks are invariant for the call, and pmap_pkru_set()/pmap_pkru_clear() retry allocation failures internally. An incremental amd64 GENERIC kernel and full module build passes, as do formatting and cppcheck. The fix is staged locally and will be pushed in the follow-up commit.

linux_emul.h declares linux_common_execve with struct image_args. Include sys/imgact.h so i386 and arm64 builds see the complete declaration and do not fail -Wvisibility.

AI-Assisted-by: Codex GPT-5
Signed-off-by: Lucas Holt <luke@foolishgames.com>
@github-actions

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Claude finished @laffer1's task in 1m 22s —— View job


Review of PR #522 (re-review — latest commit 3a32191)

  • Gather context (diff vs origin/master, changed files)
  • Review latest commit (3a32191: image activation definitions in emul MD files)
  • Check style(9) / malloc-free / bounds issues
  • Post final review

Latest commit looks correct

3a32191 adds #include <sys/imgact.h> to sys/arm64/linux/linux_emul_md.c and sys/i386/linux/linux_emul_md.c. Both files include compat/linux/linux_emul.h, which forward-declares:

int linux_common_execve(struct thread *, struct image_args *);

Without a full definition of struct image_args in scope, this declaration is only valid with the incomplete type — fine for a pointer parameter, but -Wvisibility/strict compilers can flag mismatched visibility of the type across translation units if some file sees the full struct and another doesn't. Adding sys/imgact.h (which defines struct image_args) matches the pattern already used in sys/amd64/linux/linux_pkru.c:19, which has the identical include. This is the correct, minimal fix, and resolves a real cross-arch build gap that the earlier amd64-only validation (buildkernel KERNCONF=GENERIC) wouldn't have caught, since amd64 already had the include via linux_pkru.c.

Minor style nit (non-blocking): include order in both new lines is param.h, systm.h, imgact.h, proc.h — not alphabetical (imgact.h would sort before proc.h... actually before param.h too under strict ASCII sort). This mirrors the existing non-alphabetical ordering already present in linux_pkru.c, so it's consistent with the rest of the PR rather than a new deviation — not worth blocking on.

Status of previously raised issues

All blocking issues from earlier rounds remain resolved as of this commit:

  • i386/arm64 undefined linux_pkey_alloc/_free/_mprotect — fixed via shared wrappers in sys/compat/linux/linux_misc.c:352-372, confirmed still in place.
  • linux_common/Makefile aarch64 .PATH/SRCS block for linux_emul_md.c — confirmed present (sys/modules/linux_common/Makefile:6-7,18-19), and i386's linux_emul_md.c is already wired via sys/modules/linux/Makefile:69 (i386 block, pre-existing).
  • linux_pkey_supported() stale-OSPKE check — confirmed still requires CPUID_STDEXT2_PKU, CPUID_STDEXT2_OSPKE, and xsave_mask & XFEATURE_ENABLED_PKRU.
  • fpu.c max_ext_n = flsl(xsave_mask | xsave_mask_supervisor) sizing fix — confirmed present, no OOB write into xsave_area_desc[].
  • pkey_free(0) and mprotect/pkru-tagging ordering — unchanged from upstream Linux/PKRU semantics per laffer1's earlier rebuttals; no new concerns.

No malloc/free asymmetries, bounds-check gaps, or other style(9) violations found in this commit. No assembly or contrib/mksh files touched.

Still outstanding (non-blocking, per earlier reviews): a native/cross build check for i386 and arm64 linux.ko/linux64.ko hasn't been confirmed in this sandbox (permission-restricted); the PR's own validation notes only cover an amd64 buildkernel. Worth doing before merge given this exact class of arch-specific include/symbol issue has already surfaced twice.
(branch: linux-pkey-backport)

Check fixed-boundary VM entries before changing ordinary page protections so a predictable PKRU tagging rejection cannot leave a partial update. Keep the validation in amd64_pkru_update to cover concurrent map changes.

AI-Assisted-by: Codex GPT-5
Signed-off-by: Lucas Holt <luke@foolishgames.com>
@github-actions

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Claude finished @laffer1's task in 1m 2s —— View job


Review of PR #522 (re-review — latest commit a09d98d)

  • Gather context (diff vs origin/master, changed files)
  • Review latest commit a09d98d (prevalidate pkey_mprotect ranges)
  • Check style(9) / malloc-free / bounds issues
  • Post final review

Latest commit resolves the outstanding partial-update issue

a09d98d adds linux_pkey_range_valid() (sys/amd64/linux/linux_pkru.c:52-63), which takes the VM map read lock and calls vm_map_check_boundary() on the page-rounded range before linux_mprotect_common() is invoked in linux_pkey_mprotect_machdep() (linux_pkru.c:219-255). This closes the gap flagged in the previous round: a deterministic fixed-boundary rejection can no longer leave ordinary protections changed while the PKRU tag update fails, since the same rejection now happens before protections are touched. amd64_pkru_update() (sys/amd64/amd64/sys_machdep.c:171-195) still re-validates under its own read lock when it actually applies the tag, so concurrent map changes between the two checks are still caught — the comment at linux_pkru.c:237-240 accurately describes this. The len != 0 guard correctly preserves the existing len-0 no-op path (mirrors kern_mprotect/amd64_pkru_update's own len == 0 handling).

No malloc/free asymmetry, bounds-check gap, or locking issue in the new code — vm_map_check_boundary()/vm_map_range_valid() already reject overflowing or out-of-range addr/len combinations, and this preflight uses the identical lock/check/unlock pattern already accepted in amd64_pkru_update().

Minor style nit (non-blocking): the same commit reformatted linux_pkey_supported() (linux_pkru.c:42-50) and the continuation-line indentation is now inconsistent — the second line of the && expression uses tab + 4 spaces while the third line (the parenthesized RHS of ==) uses two tabs, and the fourth line drops back to tab + 4 spaces:

	return (
	    (cpu_stdext_feature2 & (CPUID_STDEXT2_PKU | CPUID_STDEXT2_OSPKE)) ==
		(CPUID_STDEXT2_PKU | CPUID_STDEXT2_OSPKE) &&
	    (xsave_mask & XFEATURE_ENABLED_PKRU) != 0);

This is unrelated to the range-validation fix and doesn't affect correctness — worth a follow-up cleanup pass with checkstyle9.pl/indent before merge, but not blocking.

Status of previously raised issues

All prior blocking/non-blocking issues remain resolved as of this commit — i386/arm64 undefined pkey symbols (shared wrappers in linux_misc.c), the linux_common/Makefile aarch64 build block, the stale OSPKE-only feature check, the fpu.c xsave_area_desc[] sizing fix, the imgact.h include for struct image_args visibility, and pkey-0 free / PKRU encoding semantics (laffer1's earlier rebuttals hold, matching real Linux behavior).

Still outstanding (non-blocking, per earlier reviews): a native/cross build check for i386 and arm64 linux.ko/linux64.ko hasn't been confirmed in this sandbox (permission-restricted); worth doing before merge.
(branch: linux-pkey-backport)

Comment thread sys/amd64/amd64/fpu.c
return (xsave_area_desc[idx].offset);
offs = sizeof(struct savefpu) + sizeof(struct xstate_hdr);
xstate_bv &= ~(XFEATURE_ENABLED_X87 | XFEATURE_ENABLED_SSE);
while ((i = ffs(xstate_bv) - 1) > 0 && i < idx) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Low: xstate_bv is uint64_t but ffs() takes int, so any component bit >= 32 is silently dropped from the compact-offset walk. Not reachable today (the only caller passes compact=false, and no enabled component is above bit 31), but the loop is wrong as written: with a high component set in xstate_bv the loop terminates early and returns a too-small offset. ffsl() (or ffsll()) is the intended primitive.

if (len != 0 && !linux_pkey_range_valid(td, addr, len))
return (EINVAL);

error = linux_mprotect_common(td, addr, len, prot);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Low: the protection change and the key tagging are two separate operations with the map unlocked in between, so the syscall is not atomic the way Linux's is. Two consequences: (1) if amd64_pkru_update() fails after linux_mprotect_common() succeeded, the caller gets an error but the page protections were already changed; (2) vm_map_check_boundary() does not verify the range is mapped, so if another thread munmaps part of [addr, addr+len) between the two calls, pmap_pkru_set() still installs the rangeset entry over the hole and a later, unrelated mmap landing there is silently tagged with this pkey (Linux would give the new VMA key 0). Narrow race window, but worth a comment or ordering the tag before/under the same lock as the protect.

@laffer1
laffer1 merged commit 26a151c into master Sep 25, 2026
6 of 10 checks passed
@laffer1
laffer1 deleted the linux-pkey-backport branch September 25, 2026 18:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant