Before you start#
Review user copies, credentials, LSMs, integrity policy, and fault testing. Upstream Linux v6.12 is a historical teaching baseline, not a recommendation to deploy an unmaintained kernel. Choose a currently supported kernel and security-update process for real systems.
Learning goals#
Build a threat-to-control-to-evidence matrix, trace hardened usercopy and kernel memory-permission checks, distinguish exploit mitigation from correctness, and connect security controls to recovery and availability.
Source walkthrough#
- Threat model
Assets, principals, attack surface and trust boundaries
- Controls
Usercopy checks, permissions, isolation and signed code
- Evidence
Configuration, runtime state and targeted tests
- Residual risk
Failures, recovery and supported updates remain necessary
A threat model names assets, principals, trust boundaries, attacker capabilities and expected consequences. For a multi-tenant service, assets might include customer data, credentials, host availability and audit evidence. Principals include an unprivileged tenant process, a service account, an administrator and the kernel. The trusted computing base (TCB) is the set of components whose failure can defeat the stated security property; its exact boundary depends on the claim. A container shares the kernel with its host, so a kernel vulnerability can cross a boundary that namespaces and cgroups were intended to reinforce. Least privilege limits authority granted to each principal, but does not eliminate implementation bugs.
The kernel attack surface includes system calls and user copies, networking and filesystem parsers, BPF loading and helpers, module loading, device ioctls and driver input. Allocation, reference counting, object lifetime and concurrency bugs can turn malformed or mistimed input into corruption. Removing an unused feature or denying an unnecessary operation can reduce exposed paths, subject to operational needs. A mitigation is not a substitute for fixing a defect or an excuse to ignore compiler diagnostics.
Hardened usercopy: a specific check
In mm/usercopy.c, __check_object_size performs selected checks when hardened usercopy is active. This exact v6.12 GPL-2.0-only excerpt omits the preceding bypass check and subsequent stack and heap checks:
/* Skip all tests if size is zero. */
if (!n)
return;
/* Check for invalid addresses. */
check_bogus_address((const unsigned long)ptr, n, to_user);
A zero-length copy returns before the other tests. For a nonzero length, the next call checks invalid address conditions; later code examines stack and heap objects and kernel text exposure. Depending on the detected condition, the implementation can call an abort path. This is a defensive check at a usercopy boundary, not proof that every pointer, object lifetime, race or length in the kernel is safe. The function can be bypassed under its configured static-branch mechanism, and architecture/compiler/configuration determine coverage.
Consider a synthetic vulnerable ioctl implementation that computes a copy length from untrusted input but fails to establish that it fits a kernel object. A hardened usercopy check may detect a prohibited range and stop the copy under the applicable implementation and configuration. It does not automatically validate the application's protocol or prove the object was still alive. The correct code fix is to validate length, ownership and lifetime at the responsible boundary and handle errors; relying on an abort as ordinary input validation can turn a memory-safety bug into an availability failure.
In mm/usercopy.c, v6.12, SPDX GPL-2.0-only, check_bogus_address is called from __check_object_size for nonzero copies; the complete helper is:
static inline void check_bogus_address(const unsigned long ptr, unsigned long n,
bool to_user)
{
/* Reject if object wraps past end of memory. */
if (ptr + (n - 1) < ptr)
usercopy_abort("wrapped address", NULL, to_user, 0, ptr + n);
/* Reject if NULL or ZERO-allocation. */
if (ZERO_OR_NULL_PTR(ptr))
usercopy_abort("null address", NULL, to_user, ptr, n);
}
It rejects a range that wraps past the end of the address space and a NULL or zero-allocation pointer, each by calling usercopy_abort. It does not show that the object is alive, that the length matches the protocol, or that no concurrent writer changes it, and the stack and heap checks that follow it are not shown.
- Bypass or zero length
a bypassed configuration or n == 0 returns before any further test
- Bogus address
check_bogus_address rejects wrapped ranges and NULL or zero-allocation pointers
- Stack object
check_stack_object classifies copies touching the current process stack
- Heap object
check_heap_object validates the copy against heap object bounds
- Violation
usercopy_abort reports the exposure or overwrite and stops the task
Memory permissions and exploit mitigations#
W^X means writable memory should not simultaneously be executable under the intended policy; NX support permits marking appropriate memory non-executable. CONFIG_STRICT_KERNEL_RWX and CONFIG_STRICT_MODULE_RWX are version-specific configuration options where the architecture supports them. In init/main.c, mark_readonly checks configuration and rodata_enabled, flushes module init-free work, initializes jump labels for read-only state, invokes architecture mapping changes, and performs debug checks. If strict protection is disabled or unsupported, its logging branch says so. The source path is conditional; it is not evidence that the running host enabled or enforced every page permission.
Kernel address-space layout randomization (KASLR) changes placement under supported architecture and configuration, complicating some address predictions. It is not encryption and can be weakened by address disclosure or predictable boot conditions. On x86, SMEP and SMAP are hardware-supported controls relevant to supervisor execution or access of userspace mappings; on arm64, PAN serves a related but not identical privileged-access purpose. KPTI/page-table isolation addresses selected user/kernel page-table exposure on supported configurations and carries performance and compatibility trade-offs. These mechanisms are architecture- and hardware-dependent; do not infer arm64 PAN from an x86 flag or claim every system uses the same page-table layout. The kernel's self-protection documentation describes these controls as defenses with distinct coverage, including probabilistic defenses such as KASLR.
Stack protectors can detect certain stack corruption at protected function exits; they do not prevent every overflow or heap use-after-free. CONFIG_FORTIFY_SOURCE adds selected compile-time and runtime bounds checks around supported operations where object-size information is available. CONFIG_HARDENED_USERCOPY checks selected copies between kernel and userspace. CONFIG_CFI_CLANG names the v6.12 Clang control-flow-integrity configuration; its constraints, compiler support and coverage differ from both bounds checking and KASAN. KASAN is a debugging detector with overhead and mode-specific coverage, not a production guarantee of memory safety. The v6.12 security Kconfig, architecture Kconfig, x86 Kconfig and arm64 Kconfig define dependencies and availability.
Policy, signatures and residual authority#
Capabilities partition some privileged operations; LSMs add policy checks; seccomp can restrict syscall entry; lockdown and signature enforcement restrict selected kernel operations and code-loading paths. Each has a scope and failure mode. A seccomp filter cannot dereference arbitrary userspace pathname arguments; it evaluates syscall metadata, so pathname-based policy belongs at an appropriate higher-level interface. The kernel seccomp filter documentation describes this constraint. An LSM's active status does not disclose its full policy. A signed module can still contain a bug, and an allowed BPF program can still compute an incorrect result. Restricting dmesg and kernel pointer exposure can reduce useful information to an attacker but can also constrain legitimate diagnosis; access controls and auditability must be designed together.
An open file descriptor, existing mapping or delegated service endpoint can carry authority after a credential change. A security review should inventory those objects, not only effective UID and capability masks. Likewise, enabling a user namespace can help unprivileged isolation but exposes namespace-related kernel paths and depends on host policy; it is not a universal security improvement independent of workload and threat model. Control interactions and operational needs matter more than a checklist of enabled options.
The v6.12 mark_readonly branch supplies a second concrete boundary. The following exact GPL-2.0-only excerpt from init/main.c is inside the enabled branch; surrounding checks are omitted:
mark_rodata_ro();
debug_checkwx();
rodata_test();
The first call asks the architecture to make the relevant read-only data mappings read-only. The following checks examine W+X mappings and rodata behavior under their configuration and implementation. These calls execute only when the preceding strict-kernel-RWX and runtime conditions hold. A successful call does not prove that every executable mapping, module, device or userspace process follows a desired policy. The earlier flush_module_init_free_work in the same function avoids checking while queued module-init memory cleanup can leave transient mappings.
A threat-to-control-to-evidence matrix#
Consider a synthetic host running an untrusted tenant service. The asset is host integrity; the boundary is a system-call and device interface shared by containers. The threat is a tenant exploiting an out-of-bounds copy or a stale kernel object. One control is reducing exposed device nodes and capabilities; evidence would include the service's actual namespace, fd and capability configuration. Residual risk remains in allowed system calls and kernel code, and an inherited fd can expose an operation despite a restricted pathname view.
Read the text version
- Tenant out-of-bounds copy: a bad length reaches a kernel copy → Hardened usercopy: detects some invalid copy ranges (partial coverage)
- Stale kernel object: a use after the object lifetime ended → Reference and lifetime rules: keep objects alive; the fix is in the code (not covered by usercopy)
- Unsigned code load: a module or BPF load outside the trust policy → Signature and lockdown: restrict selected load paths; authenticate bytes, not correctness (trust is not a proof of safety)
- Runaway tenant load: resource exhaustion or a recovery loop → cgroup limits and watchdog: bound some failures and trigger recovery (bounds only; may loop)
For the same threat, hardened usercopy and FORTIFY can detect some invalid copy sizes. Evidence includes the exact kernel config and the relevant instrumented path, plus targeted tests in a disposable guest. Residual risk includes unsupported cases, logic errors and lifetime races. A test crash from a detected violation is evidence of an unsafe operation, not an acceptable production availability plan. KASAN or KCSAN may help reproduce a defect on a test build, but their overhead and coverage differ from production controls.
For unauthorized code loading, module signature enforcement and lockdown can restrict selected paths. Evidence requires the running policy, keyrings and actual verifier result; an EFI Secure Boot status alone is not enough. Residual risks include trusted but vulnerable code, an overly broad trust anchor and interfaces outside the protected scope. For availability, cgroup limits and a watchdog can bound some failures or trigger recovery; evidence includes effective hierarchy, tested failover and actual recovery logs. Residual risk includes shared-kernel failures, resource exhaustion outside the delegated boundary and a watchdog reset loop.
A sound matrix also names what is out of scope. For example, kernel hardening cannot by itself protect data from an administrator who controls the host's trusted boot policy and keys, unless the design has another credible trust boundary. Confidential-computing features can change a threat model on supported platforms but have their own firmware, device-sharing and attestation assumptions. State the property and evidence rather than calling a system “secure” because it has many enabled flags.
Reliability and responsible maintenance#
Security and reliability overlap but are not identical. A malformed network packet, hardware failure, accidental configuration error and malicious tenant can all trigger a failure, but their likelihood, controls and recovery differ. Design for bounded failure: isolate services, limit resources where appropriate, preserve useful logs, test restart and failover, and keep backups that have been restored successfully. A backup that was never tested may be incomplete or unusable. Watchdogs, redundancy and automatic restart can improve availability while also causing repeated destructive recovery loops if the root problem persists.
Use supported stable or vendor kernels and apply security updates through a tested process. A historical v6.12 source example does not assert that a particular deployed build has received fixes. Record exact commit or vendor release, backports, configuration and artifacts; a version string alone is not always enough. Test updates against workloads and rollback/recovery procedures without indefinitely postponing fixes. A private vulnerability report should follow the kernel's current documented security-reporting process and avoid publishing exploit details prematurely; read the current project guidance when doing so.
Compiler warnings can reveal a real compatibility or correctness problem. The chapter 08 GCC 16/C23 incompatibility is a build/toolchain issue requiring a compatible pinned compiler or reviewed fix and validation; blanket disabling WERROR or dismissing warnings is not a production hardening strategy. A passing build is still not a runtime security audit. Measure overhead and compatibility of mitigations against the threat model, and document any deliberate trade-off with its residual risk.
Failure handling is part of hardening#
A defensive check can deny an operation, abort a task or expose a bug depending on its contract. A privileged daemon that treats every failure as a reason to retry without a bound can turn a rejected request into resource exhaustion. Conversely, ignoring an error can leave partially initialized state or corrupt data. For each security boundary, identify who owns resources before the check, what must be released on failure, what is logged, and whether a less privileged caller can repeatedly reach the path. That analysis applies equally to a failed module load, a rejected BPF program and a malformed device request.
Defense in depth is strongest when controls have different failure modes. A bounds check can reject a particular invalid length; a reference protocol can keep the object alive; an LSM can restrict which principal reaches the operation; and a supported update can remove the underlying defect. None alone guarantees the others are correct. A reliability design also needs capacity for logging and recovery when a control is triggered. Rate-limited logging and protected diagnostics help avoid both silent failure and unbounded log growth, but must be evaluated against privacy and operational requirements.
Consider a service that runs with only a subset of capabilities and a restricted syscall filter but retains a writable control socket inherited from its manager. The service's UID and filter are useful evidence, yet the control socket may still grant an operation through a privileged peer. Review the protocol and authorization at that boundary, and test denial and recovery with a nonprivileged controlled client. Do not assume that stripping a capability from one process automatically removes authority delegated through another process or an already-open descriptor.
Practice#
Read-only configuration and policy inventory
On an authorized host or disposable guest, inspect available configuration and runtime indicators without changing them:
uname -r
if [ -r /proc/config.gz ] && command -v gzip >/dev/null 2>&1; then
gzip -cd /proc/config.gz | grep -E '^CONFIG_(STRICT_KERNEL_RWX|STRICT_MODULE_RWX|HARDENED_USERCOPY|FORTIFY_SOURCE|STACKPROTECTOR|CFI_CLANG|RANDOMIZE_BASE)=' || true
elif [ -r "/boot/config-$(uname -r)" ]; then
grep -E '^CONFIG_(STRICT_KERNEL_RWX|STRICT_MODULE_RWX|HARDENED_USERCOPY|FORTIFY_SOURCE|STACKPROTECTOR|CFI_CLANG|RANDOMIZE_BASE)=' "/boot/config-$(uname -r)" || true
else echo 'matching config unavailable at these paths'; fi
for f in /proc/sys/kernel/dmesg_restrict /proc/sys/kernel/kptr_restrict /sys/kernel/security/lockdown; do [ -r "$f" ] && printf '%s: %s\n' "$f" "$(cat "$f")"; done
A missing config, hidden sysctl, disabled option or permission error is a legitimate finding. A config file must match the actual running build; enabled options do not prove correct hardware enforcement or immunity. Do not write these files, weaken security, trigger an invalid usercopy or disclose sensitive logs. Record kernel, architecture and actual build provenance before interpreting the results.
Source-only alternative: fill the synthetic threat matrix with asset, attacker, control, evidence and residual risk for usercopy, module loading and service availability. For the source case, distinguish zero-length return, nonzero address check and a later violation path; for memory permissions, state the strict-RWX configuration condition. Deliver the matrix or actual read-only inventory and mark any unobserved enforcement as unknown.
Knowledge check#
- Why is hardened usercopy not a substitute for object lifetime and length validation?
- What condition gates the shown
mark_readonlycalls? - Why does a signed module remain a potential security risk?
- What authority can remain after dropping a process's effective UID?
- Why is a clean backup job log insufficient evidence of recoverability?
Answers#
- It checks selected copy ranges under its configuration; it cannot prove every object's lifetime, protocol constraint or concurrent access is valid.
- The surrounding source requires strict kernel RWX to be enabled and
rodata_enabledtrue. Architecture support and actual mapping behavior still matter. - A signature authenticates bytes under a trust policy, not the absence of vulnerabilities or compatibility defects in those bytes.
- Saved or permitted authority, open fds, mappings and service endpoints can remain, subject to their own checks. A review must inspect the actual state.
- Restoration may fail or omit required data. A tested restore under documented conditions provides stronger evidence than a job's success message.
Checkpoint#
Submit the threat matrix with at least one residual risk per control and the two source check paths. Ready means mitigations are scoped, the historical kernel is not proposed for deployment, and recovery evidence is distinguished from a backup claim. Retry if a configuration flag is treated as proof of immunity. This is not certification.