Fix a panic when writing to cgroup v2 control files from outside the sandbox
(e.g., via Cgroups.WriteControlFiles / WriteControl), where writes occur
without an open in-sandbox VFS file descriptor (fd == nil). cgroup.procs now
safely falls back to context credentials with unconfined namespace migration,
and checkNSDelegateWrite skips namespace delegation checks. Add an integration
test for runsc cgroup v2 control file writes.
Also:
- Export cgroup2fs.InitHierarchy to initialize sandbox-wide cgroup2 state
and root controller enablement.
- Plumb CgroupNamespace through sentry lifecycle and task context to derive
initial container cgroup2 placements.
PiperOrigin-RevId: 978087817
Added pausing support for the timer(syntheticTimer)
the `AfterFunc` now won't trigger
when the kernel is paused.
The timer waits for all the AfterFunc functions
to complete during Pause. As of right now, all the
AfterFunc callbacks are not heavy, so it's okay.
When a new afterFunc is created:
- The event is added to the synthetic timer queue.
- AfterFuncScheduler sleeps until the next event in the queue.
- Triggers the expired events and repeats the cycle.
PiperOrigin-RevId: 971771243
GPU profilers (Nsight Systems/CUPTI) build their trace timeline from
CLOCK_MONOTONIC_RAW. gVisor serves CLOCK_MONOTONIC_RAW as an alias of
CLOCK_MONOTONIC, this will drift from CLOCK_MONOTONIC_RAW breaking profiling.
When nvproxy is enabled with `capProfiling`, this change exposes
CLOCK_MONOTONIC_RAW as a clock distinct from CLOCK_MONOTONIC that tracks the
host's CLOCK_MONOTONIC_RAW (absolute, unadjusted). CLOCK_MONOTONIC is
unchanged. When profiling is disabled, CLOCK_MONOTONIC_RAW continues to alias
CLOCK_MONOTONIC as before.
When --mount-cgroup-v2 is enabled, a container whose spec includes a
cgroup mount is now given its own cgroup "/<cid>" in the cgroup2
hierarchy, with its init process placed there.
Cgroup namespaces from the spec are now honored, mirroring PID
namespace policy: no entry means the root namespace, an entry with a
path joins the container that declared the same path, and an entry
without a path gets a new namespace rooted at the container's cgroup.
Since a fresh cgroup2 mount is rooted at the mounting namespace's
root, a private-namespace container's /sys/fs/cgroup shows only its
own subtree, as on Linux, with no need for bind mounts.
At boot the sentry now enables nsdelegate and delegates controllers
to per-container cgroups via the root cgroup.subtree_control. Exec
sessions join the container's cgroup and cgroup namespace, like
runc exec. Per-container "runsc events" and read-control queries
now resolve for any container with a cgroup mount.
Note two differences from the existing v1 support. In v1 we bind mount:
the per-container dir "/$cid" of each controller hierarchy appears as
/sys/fs/cgroup/<controller>. With v2 no bind mounts are needed: a fresh
cgroup2 mount is rooted at the mounting namespace's root, so honoring
the spec's cgroupns request yields the scoped view naturally. (Binding
a subtree a la v1 was not an option anyway: systemd explicitly rejects
a sub-hierarchy mounted without a matching cgroup namespace.) Second,
on container destroy we now make a best-effort, recursive attempt to
remove the container's cgroup subtree; v1 leaves its per-container
dirs behind.
The root container is not special-cased. As with v1, where even the
root container gets container-id-stamped cgroup dirs and the stats
queries they enable, it gets its own "/$root_cid", and its cgroup
namespace comes from the spec like everyone else's. Docker and
Kubernetes both request a private cgroup namespace for every container
on cgroup2 hosts, so in practice the root container sees its cgroup as
a pleasing "/" rather than "/$root_cid"; a spec with no cgroupns entry
(e.g. docker run --cgroupns=host) instead sees the full sandbox
hierarchy, as on Linux. One consequence of the private default: in a
multi-container sandbox the root container cannot see the other
containers' cgroups (namespace roots are not hierarchical). I don't
expect that to be problematic, and a spec without a cgroupns entry
regains the full view if ever needed.
PiperOrigin-RevId: 960153510
cgroup namespaces are non hierarchical and they are defined primarily by a
"root" cgroup node. The init cgroup namespaces's root node is the true root of
the (only) cgroup2fs tree. The root node of other cgroup namespaces is the
cgroup node of the task at the time of namespace creation.
Residing in a cgroup namespace affects tasks this way:
1) The views into /proc/[pid]/cgroup and the "root" field under
/proc/[pid]/mountinfo are virtualized: they are rendered relative to the
reading task's cgroupns.
2) mount()ing a cgroup2 fs furnishes only a subtree of the full cgroup2fs
tree, rooted at the "root" cgroup of the hosting cgroupns.
Fixes#11989
PiperOrigin-RevId: 948882249
This change refactors the network configuration flow during sandbox restore
and defers the creation of network links and routes in runsc to run after
seccomp filters are installed.
- Replaced ReplaceConfig with ResetConfig in inet.Stack. Introduced
inet.NetworkArgs to configure the stack during restore, updating k.LoadFrom
to use it.
- Renamed CreateLinksAndRoutes URPC to SetNetworkArgs, which now configures
host sockets before seccomp is installed, deferring link/route creation to run
after seccomp.
- Fixed S/R support by saving and restoring limit/burst state.
- Minor seccomp filter adjustments and test updates.
PiperOrigin-RevId: 945995430
The cgroup v2 fs is initialized early in the boot process, so that every task
in the sandbox resides in at least the root v2 cgroup. A new runsc flag
--mount-cgroup-v2 has been added: this controls whether runsc mounts a
cgroupv2 filesystem at /sys/fs/cgroup automatically (currently it always
mounts a v1 hierarchy at this path). This flag also gates whether userspace
can mount a cgroup2 fs. When this flag is false, which is the default, all
tasks in the sandbox reside forever in the root cgroup.
Like in Linux, the v2 hierarchy is deemed the default owner of a controller,
that is, until the corresponding v1 hierarchy is mounted: then, the v1 tree
steals ownership of that controller and it remains this way till as long as
a v1 mount exists. In other words, hybrid mode is supported, even if such
workloads are rare these days.
The pids controller is the only controller implemented in full: it serves
as validation for the unified hierarchy. The memory controller can be used
to learn the usage stats of a subtree: but like the current v1 implementation,
and unlike Linux, it doesn't achieve this through charging up the hierarchy
during allocation, instead it queries pgalloc for every active controller's
id. And it does no enforcing. The cpu and cpuset controllers are mere stubs,
like in the current v1 implementation.
The following are supported:
* General matters of topology: cgroup.subtree_control, cgroup.controllers,
cgroup.procs etc
* CLONE_INTO_CGROUP
* cgroup.kill
* cgroup.events
* A working pids controller and its events files
* A minimal memory controller that just reports mem usage
* Stubbed cpu and cpuset controllers
The following are NOT supported:
* threaded cgroups
* cgroup namespaces
* BPF_PROG_TYPE_CGROUP_DEVICE
* cgroup.freeze
Updates #13007
PiperOrigin-RevId: 943519748
Previously, during sandbox restore the network configuration was restored by
creating a new network stack (which has all the config scraped from the host)
and the replacing this new config such as NICs, routes and IP addresses from
the new stack to the loaded/restored stack.
This change refactors the process by introducing a new `inet.NetworkArgs`
interface that abstracts how the network stack is configured. The `Loader` now
implements this interface and configures the loaded stack directly using the
configuration scraped from the host during restore. `Kernel.LoadFrom` accepts
this interface (instead of stack), allowing it to configure the restored stack
using either the Loader's configuration.
PiperOrigin-RevId: 941330876
Adds the infrastructure for a DynamicBytesFile to become pollable
(perhaps with select, poll, or epoll). Simply arrange for a
DynamicBytesPoller to be notified whenever the relevant file is updated
and implement PollableDynamicBytesSource on the DynamicBytesSource.
EPOLLPRI and EPOLLERR are triggered for these files, which seems to
match Linux's implementation for /proc/sys files.
The first two files to support this are hostname and domainname in
/proc/sys/kernel. This is used by systemd-journald to monitor for
hostname changes.
Timekeeper.startUpdater drove a timer that re-armed every update interval
for the lifetime of the sentry, so a fully idle sandbox (no runnable tasks)
woke the sentry on every tick only to rewrite VDSO clock parameters that
nothing could read.
Make the updater park itself while nothing needs a fresh clock, mirroring the
CPU clock ticker's idle handling. The updater goroutine's state is held in an
atomic, Timekeeper.updaterState: a lifecycle state, updaterStopped, plus three
running states ordered parked < idle < active. updaterState is mutated by the
updater goroutine's tick, by addRef, and by start/stopUpdater; all hold updateMu
except addRef's lockless idle->active CAS, the fast path that re-activates an
idle updater.
Pinning the updater active is a reference count (Timekeeper.refs):
- addRef pins the updater active until the matching release, waking and
re-calibrating it if it had parked; release drops the reference. The goroutine
winds down one step per idle tick (active->idle, then idle->parked) and re-reads
refs after each store, while addRef adds its reference before reading the state
-- store-then-load on both sides -- so a referenced updater is never left winding
down. addRef is lockless when the updater is active (nothing to do) or idle (a
single idle->active CAS); only waking a parked updater takes updateMu, where it
re-calibrates (the params have drifted) before returning so the caller never reads
a stale clock.
- incRunningTasks takes a reference on the 0->1 task transition and
decRunningTasks drops it on 1->0, so the updater stays active for as long as
any task can read the VDSO. The reference is taken before the running count is
published, so a concurrent resumer that observes the count cannot read a
drifted clock. incRunningTasks holds runningTasksMu across addRef; the lock
order is always runningTasksMu -> updateMu (the updater goroutine reads refs
locklessly and never takes runningTasksMu), so this cannot deadlock, and only
a task resume from a fully idle sandbox re-calibrates under that lock.
- GetTime brackets the actual clock read with addRef/release, so the updater
stays active -- and the clock fresh -- for the whole read, even if the reader
is descheduled in the middle of it. The read is lockless unless it finds the
updater parked -- i.e. the sandbox has been idle long enough (a full update
interval) for the updater to park -- when addRef takes updateMu to wake and
re-calibrate it.
The kernel drives the updater entirely through addRef/release; the Timekeeper
reads no kernel state. refs is nosave -- at save time no task is running and no
read is in flight, so it is zero, and it is rebuilt as tasks resume and reads
occur.
The VDSO parameter page has a single writer, so the sample-and-write step is
factored into Timekeeper.update() and every refresh holds updateMu across it: the
updater goroutine's tick, startUpdater's activation refresh (only when it starts
with a reference already held), and addRef's parked slow path. updaterState's
idle->active CAS is lockless, but every transition that publishes fresh parameters
(out of parked or stopped) holds updateMu, so a reader that observes a non-parked
state can rely on the parameters being fresh.
updaterStopped is a lifecycle state, distinct from the idle wake path, that keeps
PauseUpdates/Destroy correct: stopUpdater publishes updaterStopped under updateMu
before it closes stop and waits for the goroutine, so an addRef arriving afterward
cannot restart the updater or rewrite the page (it still counts the reference, but
leaves the state stopped). While stopped, a runnable task may execute in
sentry/system mode; startUpdater (via ResumeUpdates) restarts the updater parked,
or active if a reference is already held. Only startUpdater leaves updaterStopped.
The Clocks.Update interface gains a parked bool: on resume from a parked period
the gap was unobserved and at least one update interval long, so the calibrated
clock re-calibrates to the freshly sampled parameters rather than slewing the
accumulated drift forward over a single interval.
The updater starts parked rather than active, activating on the first reference.
A fully idle sentry performs no periodic clock wakeups; freshness on resume and on
internal reads is preserved.
Co-authored-by: Hanzo Dev <[email protected]>
This change fixes the issue by surfacing the CPU accounting gVisor already tracks, read as plain atomics, closish to how Linux serves `/proc/stat`:
- **`cpu` aggregate line:** served from kernel-wide cumulative user/system CPU accumulators advanced by the existing CPU-clock ticker (analogous to Linux's `kcpustat`), read as two atomics. Idle time is approximated from uptime, giving monitoring tools meaningful deltas between samples.
- **Per-CPU lines (`cpu0`..`cpuN`):** since gVisor has no per-core accounting, the aggregate is distributed evenly across application cores (remainder assigned to `cpu0`), so the per-CPU lines sum back to the aggregate.
- **`procs_running` / `procs_blocked`:** O(1) reads of kernel atomic counters maintained on task scheduling-state transitions, analogous to Linux's `nr_running`/`nr_iowait`, instead of being hardcoded to zero.
`intr`, `ctxt`, `processes`, and `softirq` remain stubbed -- a global fork counter would be needed for `processes`?
Verified end-to-end with a `runsc` build: `top` and `htop` now report live CPU usage under load.
Fixes#9967.
Co-authored-by: Hanzo Dev <[email protected]>
Watchdog.loop() armed a fresh time.After(period) timer for the lifetime of
the sentry (period = TaskTimeout/4, so 45s by default), so a fully idle
sandbox woke the host roughly every 45s only to scan a task set in which
nothing could be stuck.
The watchdog only reports tasks stuck in TaskGoroutineRunningSys. Such a task
counts as a running task (Kernel.runningTasks), so while runningTasks is 0
there is none to find; moreover the kernel CPU clock that the watchdog uses to
measure how long a task has been in RunningSys is frozen while no task is
running. So an idle sandbox has nothing for the watchdog to detect, and the
periodic scan is pure overhead that needlessly wakes the host.
Make the watchdog park while no tasks are running, mirroring the CPU clock
ticker's and Timekeeper's idle handling. The loop now blocks in
Kernel.WaitForTaskActivity() until a task becomes runnable instead of arming
its periodic timer, and reuses a single timer that it leaves stopped while
parked, so an idle sentry arms no host timer.
WaitForTaskActivity() returns immediately if a task is already running;
otherwise it blocks on activeNotifyCh, a channel that incRunningTasks() closes
on the 0->1 running-task transition and replaces for the next idle period.
Closing broadcasts to every parked waiter at once, and the channel is read
under runningTasksMu so the wakeup cannot be lost. A channel (rather than
runningTasksCond) lets the watchdog also select on its own stop channel, which
a sync.Cond cannot. The close fires on every 0->1 transition, so a waiter that
parked during the CPU clock ticker's one-tick slack window, or before
Kernel.Start(), is still woken.
Tasks executing application code (TaskGoroutineRunningApp) also count as
running, so a sandbox busy purely in userspace keeps the watchdog awake even
though no task is in RunningSys. That is harmless -- the host is busy running
guest code then anyway -- and tightening it is left for a later change.
A fully idle sentry now performs no periodic watchdog wakeups; stuck-task
detection while tasks are running is unchanged.
Co-authored-by: Hanzo Dev <[email protected]>
- Fix sequential cuda-checkpoint timeline logic to not use MultiFork(). So all
cuda-checkpoint invocations appear sequentially in the same column.
- Add a timeline for Kernel.LoadFrom() which shows time consumed by each step.
- Remove the per-container restore timeline. It was mostly noise and had
uninteresting details. Instead added a point on the main restore timeline
about when we got each container's restore signal.
- Add more useful timeline points in containerManager.Restore() and
restorer.restoreContainerInfo().
PiperOrigin-RevId: 930078736
User namespace entries under `/proc/[pid]/ns` currently render as fake
namespace symlinks. They look like the other namespace files, but opening
them does not produce an `nsfs` file that `setns(2)` can use. Rootless
container tools such as `buildah` and `podman` rely on that file when they
re-enter the pause process user namespace, so the second lifecycle command
fails with `EINVAL`.
Make `UserNamespace` implement `vfs.Namespace` and give each user namespace
an `nsfs` inode when it is created. `/proc/[pid]/ns/user` now uses the
regular namespace symlink path, so opening it returns a joinable namespace
file instead of a fake link target.
`Setns` now accepts `CLONE_NEWUSER` from both `nsfd`s and `pidfd`s. It
follows the Linux restrictions for user namespace joins by rejecting the
caller's current user namespace, requiring `CAP_SYS_ADMIN` in the target
user namespace, rejecting multithreaded callers, and rejecting callers with
`fs` state shared outside the thread group. The capability checks for any
other namespaces in the same `setns` call use the credentials the caller
would have after joining the user namespace.
Add a syscall regression test that creates a child user namespace, opens
`/proc/<pid>/ns/user`, and verifies that `setns(CLONE_NEWUSER)` succeeds.
Fixes https://github.com/google/gvisor/issues/13314
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/gvisor/pull/13323 from shayonj:issue-13314-userns-setns 8060b5f91ba1f2e9c7b6746da233d644728be2e9
PiperOrigin-RevId: 926855389
Refactor IOUringEnabled from being a global variable in pkg/sentry/kernel
to being a member of the kernel.Kernel struct. This improves encapsulation
and allows a consistent view between save/restore sessions.
PiperOrigin-RevId: 912220031
Note that runsc wait --fsrestore only waits for filesystem restore operations
to complete for the given container, rather than the whole sandbox (like wait
--fscheckpoint, --checkpoint, and --restore do).
PiperOrigin-RevId: 886982088
- `runsc fscheckpoint` saves a filesystem checkpoint, analogous to `runsc
checkpoint`. `-direct` and `-leave-running` flags are supported for
compatibility with `runsc checkpoint`. `-save-restore-exec*` is currently
not supported due to lack of a use case. `-compression` support is not
planned (specific files in filesystem checkpoint images may use compression
as appropriate, but this is an implementation detail).
`-exclude-committed-zero-pages` is inapplicable.
- `runsc create -fs-restore-image-path` restores a filesystem checkpoint. The
`-fs-restore-direct` flag has the same effect as `runsc restore -direct`.
The behavior of `runsc restore -background` is always in effect for
filesystem checkpoints.
PiperOrigin-RevId: 885914969
... and use it everywhere in preference to its string. This is to be able to
reliably inspect pgalloc.MemoryFile.ResourceID().Path in cl/879147587.
PiperOrigin-RevId: 883274090
Add PageSize checks in Kernel.SaveTo() and Kernel.LoadFrom() to return
a clean error when save/restore is attempted with non-4K page sizes.
This is similar to the checks added for KVM and Systrap platforms.
Save/restore serialization has not been adapted for 64K pages yet,
so this gives a clear error message instead of a cryptic failure.
Previously, external connections were terminated during both restore and
resume operations, causing a reconnection after every resume. By limiting
termination to the restore only, connections now remain active during resumes
as intended.
Added tests to verify that resume does not terminate the connection.
PiperOrigin-RevId: 868830577
The correctness of save/restore depends on the kernel object graph remaining
constant while it is being saved/restored. Allowing fdnotifier to send host FD
readiness notifications during save/restore violates this requirement.
PiperOrigin-RevId: 856472612
- Introduce Credentials.HasRootCapability().
- Rename Credentials.HasCapability() to Credentials.HasSelfCapability().
- Rename Task.HasCapability() to Task.HasSelfCapability().
- Introduce inet.Namespace.HasCapability(ctx).
- CapError(), invoked by unimplemented syscalls' stubs, now checks
against the root userns.
All towards making it less easy to evaluate creds relative to self userns
instead of root userns in the future.
PiperOrigin-RevId: 853942212
This change adds RSEQ support for platform/kvm. RSEQ is disabled by default.
To support RSEQ, the KVM platform must provide two key capabilities:
(1) A stable, unique CPU ID.
(2) A way to detect when a thread as been preempted.
This implementation provides the necessary support as follows:
CPU ID:
- platform/kvm now advertises the KVM vCPU ID as the cpu_id
Preemption Detection:
- Compares the last context the CPU ran against the current context being
scheduled.
- Compares the context's rseqCPU and CPU ID retrieved by the platform
To facilitate this, several new methods are introduced to the platform
interface and implemented by platform/kvm:
- HasCpuNumbers()
- NumCPUs()
- DetectsCPUPreemption()
- PreemptCpu()
- PreemptAllCpus()
A new user-facing KVM specific option, use_cpu_numbers, is introduced to
effectively feature guard RSEQ. When use_cpu_numbers is true, vCPU IDs are
exposed to the guest, enabling RSEQ support. Setting it to false reverts to
the previous behavior and effectively disables RSEQ for applications that
rely on it.
PiperOrigin-RevId: 844846226
The net-disconnect-ok flag was previously used only to cause a panic in the
beforeSave() method of TCP and unix connections if it was set to false.
Since this flag is now set to true by default, and this default setting causes
TCP and unix connections to always be aborted during the save operation, the
flag no longer serves its original purpose of conditionally preventing
connection termination. So, the flag's usage is removed.
PiperOrigin-RevId: 840433518
Before this change, the processors were closed in beforeSave which caused the
packet processing for established connections to stop even in the case of
save/resume. Do not close in beforeSave, instead close these processors in
afterLoad for restore.
PiperOrigin-RevId: 821714849
Aside from improving checkpointing performance by using AIO to better utilize
disk bandwidth, this makes it possible to parallelize writing pages with
scanning for zero pages (in a following CL).
As part of this, update and document ownership semantics for the state file,
pages metadata file, and pages file, with the following goals:
- Since io.WriteCloser.Close() may flush buffers, it must return a nil error
before write success is reported
- The pages metadata file is closed while async page saving is in progress (to
hide the latency of the former)
PiperOrigin-RevId: 814315485
During restore there will be new NICs and routes created based on the network
config. Close these old NICs and routes in beforeSave() only for save/restore.
For save/resume, do not close them.
As new link endpoints are created during restore, there is no need to start
the associated old processor goroutines in afterLoad. Start them only for
save/resume.
PiperOrigin-RevId: 804994281
There are two issues being fixed: 1) checkAndPreventSharingOutsideTG() was
accessing t.fsContext without grabbing t.mu and thus causing a data race with
a concurrent unshare and 2) the concurrent unshare might present an incomplete
view of the shared fscontext and cause execve to incorrectly conclude that it
was shared outside the thread group.
Reported-by: [email protected]
Reported-by: [email protected]
PiperOrigin-RevId: 800327461
Currently, on certain error paths during task creation (e.g., when
"runsc exec" fails), we do not properly handle the refcount of the
mntns/FSContext, leading to a refcount leak. This patch adds cleanup
that would be released before the reference ownership is transferred
to callee, ensuring that the refcount is correctly decremented.
Fixes#12054
Signed-off-by: Tianyu Zhou <[email protected]>
There is already some support for privileged executables before this change:
file capabilities were taken into account when creating new processes inside
the sandbox from external instigation (but not during execve(2)). set-user-id
and set-group-id bits were never taken into account, and the Sentry behaved
as if NO_NEW_PRIVS is always true. After this change, execve(2) and the
external Exec entry point both take the set-user(group)-id bits and file
capabilities into account.
Because NO_NEW_PRIVS can no longer be assumed to be always set, a couple of
extra checks to prevent privilege elevation during execve(2) are needed:
the presence of a ptracer and the use of a shared fsContext. Moreover,
execve had to be serialized with seccomp(2) when it is syncing filters to
all threads in a group. Serializing execve with PTRACE_ATTACH and seccomp
required some Task runState gymnastics.
Task dumpability is reset when privilege elevation takes place and privilege
elevation is denied for binaries living on nosuid mounts. To fix an old bug
where some aux vectors were populated using data from the old pre-execve
creds, the computation of the (file-privilege-augmented) creds during execve
now takes place before the stack for the new program is set up.
gvisor users who thus far have exec'd into the sandbox with UID as (sandbox)
root will lose the caps an all-powerful namespace root'd have if: 1) the binary
is a suid/guid binary 2) the owner of the binary is host root and there is
no mapping for host root in the host user namespace of the sandbox. To avoid
this surprise, the suid/sgid bits are ignored by default. Users can opt-in
via the --allow-suid flag.
One result of this change is that sudo now works inside the sandbox.
PiperOrigin-RevId: 794414101
When using async page loading (the checkpoint image has compression=none), the
kernel and memory files are loaded in parallel. A step in kernel loading tries
writing to the main MemoryFile. Ensure that step happens after the main
MemoryFile has started being loaded (i.e. mainMF.LoadFrom() method has returned
successfully). MemoryFile.asyncPageLoad field needs to be initialized before
it can be written to.
Fixes#11842
PiperOrigin-RevId: 779995419
- During restore if endpoints' states are not loaded before starting
dispatchers, then the incoming packets will be queued and the dispatcher will
try to send the packets, which will result in a panic as the endpoint might
still be in an invalid state (eg: initial).
- To avoid this, wait for all the endpoints to be loaded completely before
starting the dispatchers by calling AsycnLoading.Wait() in stack.Restore() and
remove this from kernel.LoadFrom()
- Added a test to check save restore of multiple(100) listening sockets.
PiperOrigin-RevId: 762010964
All the following changes are compatible with what Linux does.
- Do not attempt to apply file capabilities when it is not present.
- If the rootID specified in v3 file capability struct does not own the
current userns, then file capabilities are not applied.
- While applying file capabilities, EPERM is only returned if
VFS_CAP_FLAGS_EFFECTIVE is set.
- Re-raise capabilities of root user as per capabilities(7). Compare
security/commoncap.c:handle_privileged_root().
PiperOrigin-RevId: 761725759