kernel: GKI-wide compile compat + dcache fixes - #31
Conversation
Verified compiling clean across the full GKI/AOSP range (4.9, 4.14, 4.19, 5.4, 5.10, 5.15, 6.1, 6.6, 6.12, 6.18) via a per-version compile matrix, and device-tested on android16-6.12 (OnePlus 15). Version-boundary compile fixes (were breaking specific kernels): - 4.9: getattr is 3-arg (vfsmount,dentry,kstat) and vfs_getattr_nosec is 2-arg; guarded to <4.11 (upstream called the 4-arg statx forms unconditionally). - d_revalidate: 4-arg form guarded at >=6.13, not >=6.11 — android16-6.12 (and 6.6) are still 2-arg; only 6.18 is 4-arg, so the old >=6.11 guard broke 6.12. - 6.18: vm_area_desc.file is const in nm_mmap_prepare -> assign via a cast. - pre-4.11: nm_alloc_rule used the 4-arg statx vfs_getattr + STATX_* -> 2-arg. - pre-4.11: idr_remove() returns void -> probe with idr_find() first. dcache consistency: - NM_SET_DOPS(): when we set d_op=&nm_dops on a dentry whose superblock is an overlayfs (its s_d_op carries DCACHE_OP_* flags), the stale op-flags made the VFS call NULL ops -> OOPS on readdir/`..`. Clear all DCACHE_OP_* and set only DCACHE_OP_REVALIDATE at every d_op assignment site. - nm_d_revalidate: drop a stale injected child when its parent is no longer hijacked (ghost dentry survived drop_caches until reboot), and make the result UID-aware so a blocked reader's negative dentry can't pollute an unblocked one. - nm_dir_lookup: resolve a virtual-dir parent's dir_node from i_private so a new multi-child subtree (e.g. app libs under a synthesized dir) is served correctly.
Compile-tests fs/nomount.o against the canonical AOSP/GKI tree for every supported kernel (4.9, 4.14, 4.19, 5.4, 5.10, 5.15, 6.1, 6.6, 6.12, 6.18) on push and on PRs touching kernel/src or kernel/patches. Catches version-boundary API breakage (the class of bug the dcache/compat commit fixes) before it ships. - 4.x use the distro gcc-aarch64-linux-gnu (gcc-13); the AOSP gcc-4.9 prebuilt is GCC 4.9 which android-common 4.x rejects (needs >=5.1). - 5.4+ use clang with LLVM=1 + unversioned packages (the versioned LLVM=-NN suffix isn't understood by pre-6.1 kbuild) and CROSS_COMPILE for the target triple. - 6.18 source is git.kernel.org linux-6.18.y; the rest are android.googlesource.com. - ccache + kernel-clone caching keep re-runs fast; a summary job renders a pass grid.
|
Hi, personally I don't like these workflow, you can remove or revert it? |
Add inline function to install dentry operations instead of a macro.
i'm editing in github directly, so i make some mistakes hehe
|
Could you remove the workflow from this PR and add it to another PR? Since I don't want to add it for now, but later when the PR #23 is merged, I have plans to add certain workflows, and I could merge your workflow. |
I just saw the change you made in the PR description, and well, it's the expected change. NoMount currently works just like bind mount, so that observation is completely normal. I am also working on an OverlayFS-style system where I can merge different folders (like |
|
Agreed on all counts — bind-mount semantics make the partition-root behaviour correct, and the fault was my walker emitting that target. I've corrected the note in the description and dropped the On Concretely, on a OnePlus 15 (android16-6.12) with a 139-overlay RRO module (OxygenCustomizer) injected file-by-file into
Cost of doing it per-file at that scale: the full pass (clear + re-inject 171 rules) takes ~70 ms, and the rules occupy roughly 60 KB of slab ( If you do build it, two things I'd pre-decide, both from the same ambiguity that bit me: keeping replace as the default for a directory target (an explicit Not asking for the feature — the current design covers my use case completely. |
This reverts commit 4c0b607.
Summary
Kernel changes only: makes
nomount.ccompile clean across the whole AOSP/GKI range, and fixes two real dcache bugs found on-device.Verified: 10/10 green across 4.9, 4.14, 4.19, 5.4, 5.10, 5.15, 6.1, 6.6, 6.12 and 6.18, and the engine is device-tested on android16-6.12 (OnePlus 15). That matrix still runs in my fork, so these commits stay validated across the range without adding any workflow here.
Kernel fixes
Version-boundary compile breaks (each broke a specific kernel that the current unconditional calls don't handle):
getattris 3-arg(vfsmount, dentry, kstat)andvfs_getattr_nosecis 2-arg → guarded to<4.11.>=6.13, not>=6.11— android16-6.12 and 6.6 are still 2-arg; only 6.18 is 4-arg. The old>=6.11guard broke 6.12.vm_area_desc.fileisconstinnm_mmap_prepare→ assign via a cast.nm_alloc_ruleused 4-arg statxvfs_getattr+STATX_*→ 2-arg form.idr_remove()returns void → probe withidr_find()first.dcache consistency:
nm_install_dentry_ops()(wasNM_SET_DOPS()) — whend_op = &nm_dopsis set on a dentry whose superblock is an overlayfs (itss_d_opcarriesDCACHE_OP_*flags), the stale op-flags made the VFS call NULL ops → OOPS on readdir /... Now everyd_opsite clears allDCACHE_OP_*and sets onlyDCACHE_OP_REVALIDATE.nm_d_revalidate— drop a stale injected child when its parent is no longer hijacked (ghost dentry that surviveddrop_cachesuntil reboot), and make the verdict UID-aware so a blocked reader's negative dentry can't pollute an unblocked reader.nm_dir_lookup— resolve a virtual-dir parent'sdir_nodefromi_private, so a new multi-child subtree (e.g. app native libs under a synthesized dir) is served correctly.How the range was verified (no CI added here)
The matrix lives in my fork now, not in this PR. It builds only
fs/nomount.o(not a full kernel) per version, which is what makes 10 kernels cheap to check:gcc-aarch64-linux-gnu(gcc-13); the AOSP gcc-4.9 prebuilt is GCC 4.9, which android-common 4.x rejects (needs ≥5.1).LLVM=1+ unversioned packages (the versionedLLVM=-NNsuffix isn't understood by pre-6.1 kbuild) andCROSS_COMPILEfor the target triple.git.kernel.org linux-6.18.y; the rest fromandroid.googlesource.com.Happy to bring it over as a standalone PR after #23, shaped however you prefer.
Note (not in this PR): hookless RRO
While validating on-device, I confirmed RRO overlays work with pure hookless injection — no overlayfs mount: inject the overlay APKs into
/product/overlayin post-fs-data;OverlayManagerServicescans later in system_server and registers them,idmap2reads the injected APK (via themmap_prepareredirect) and builds a valid idmap, andcmd overlay enableworks. That makes an RRO-using setup fully mountless. It's a downstream module-side change, but it exercises this engine's mmap/getattr path — happy to share details if useful upstream.Note (not in this PR): readdir merge validated at scale, and a downstream walker pitfall
Scaling that RRO work up produced some measurements worth recording for the dcache/readdir paths this PR touches, plus one platform hazard that cost me a lot of bootloops to diagnose.
The engine's readdir merge is correct. Injecting into a populated stock directory (
/product/overlay, 78 real entries) and listing it:Real entries and injected children are both emitted, deduped by name, across the multi-
getdentssequence innomount_hijacked_iterate_dir— no masking at any scale. (This is the per-file bind-mount behaviour working as designed, not directory merging.)The pitfall is a walker that emits a bare partition root as a target.
nm add /product <backing>redirects the whole partition, so every stock entry under it disappears — correct for the rule, but not what my module meant to ask for. On OnePlus/Oppo the consequence is specifically fatal at boot: with the stock/product/overlay/OplusGmsConfigOverlayCommonreplaced, zygote'sOverlayConfigpreloads the stock twin at/my_product/cust/<region>/overlay/…instead, and/my_productis not in zygote's FD allowlist — soFileDescriptorInfo::CreateFromFdraisesat the first
forkSystemServer(before system_server or OMS runs) → SIGABRT → bootloop, independent of how many overlays were injected.Upstream's walker is not affected, for a reason worth keeping:
module/metamount.shusesfind -L, so a module'ssystem/<partition>symlink is followed and descended rather than emitted as an entry, and every emitted path is at least two components deep. Two things reintroduce the hazard downstream — mappingsystem/product→/product(SAR aliases), and any walker whose file-type check does not follow symlinks (e.g. Rust'sfs::FileType, which seessystem/product -> ../productas "not a directory" and injects it as a leaf). Both applied to my module, and the fix was simply to skip targets that resolve to a single-component path.So this is a note for module authors rather than a request for an engine change: keep partition-root paths out of what you feed
nm add, and avoid/my_product-style non-allowlisted partitions as injection targets for anything zygote may preload — that one is a platform constraint no walker can work around.Fixes were developed against
experimental/hooklessand rebased ontobf285ba. No upstream feature is dropped — the diff is our additions plus thebeforeside of the lines they modify.