firefox-fourier: 0005 RDD sandbox carve-out for V4L2 stateless decode
Extends Mozilla's RDD sandbox to permit /dev/media* (driver-matched),
the MEDIA_IOC_* ioctl family ('|'), and the sysfs paths libudev would
need to enumerate the media controller (read-only AddTree on
/sys/class, /sys/bus, /sys/dev/char, /sys/devices/platform plus
/run/udev, /etc/udev/udev.conf, /proc/self, /dev/dma_heap).
Necessary but not sufficient on its own: Mozilla's OpenAtTrap
rejects fd-relative openat used by systemd's chase() inside libudev.
The companion ffmpeg-v4l2-request-git patch adds a brute-force
fallback that opens /dev/media[0..15] directly with absolute paths,
which composes with this broker policy.
Validated on RK3399 / Pinebook Pro / mainline rkvdec: with both
patches in place, default RDD sandbox runs HW decode at ~5% CPU on
1080p30 H.264 (vs ~64% software fallback before). Closes the
parity gap with MOZ_DISABLE_RDD_SANDBOX=1 baseline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,236 @@
|
|||||||
|
From: Markus Fritsche <mfritsche@reauktion.de>
|
||||||
|
Subject: [PATCH 5/5] security/sandbox/linux: extend V4L2 sandbox carve-out
|
||||||
|
to /dev/media* and the MEDIA_IOC_* ioctl family for stateless decode
|
||||||
|
Date: 2026-04-29
|
||||||
|
|
||||||
|
Background
|
||||||
|
----------
|
||||||
|
The existing V4L2 sandbox carve-out (`AddV4l2Dependencies` in
|
||||||
|
`SandboxBrokerPolicyFactory.cpp` + the `'V'` ioctl-type allow rule
|
||||||
|
in `SandboxFilter.cpp`) is sufficient for the **stateful** V4L2-M2M
|
||||||
|
codec wrapper (`h264_v4l2m2m` etc.), where every operation goes
|
||||||
|
through `/dev/video*` and uses `VIDIOC_*` ioctls.
|
||||||
|
|
||||||
|
Stateless V4L2 decoders (Rockchip rkvdec, hantro on mainline kernel,
|
||||||
|
Allwinner cedrus, RPi5 codec_request) drive a different shape:
|
||||||
|
|
||||||
|
* Per-frame request lifecycle is queued via the *Media Controller*
|
||||||
|
node (`/dev/media*`), not the video node. `MEDIA_IOC_REQUEST_ALLOC`
|
||||||
|
creates a request fd, which is then attached to OUTPUT-queue
|
||||||
|
`VIDIOC_QBUF` calls and queued/reinited via `MEDIA_REQUEST_IOC_*`
|
||||||
|
ioctls.
|
||||||
|
* Both ioctl families use type `'|'`. The current RDD seccomp
|
||||||
|
filter only allows `'d'` (DRM), `'b'` (DMA-Buf), and `'V'`
|
||||||
|
(V4L2). `'|'` is rejected, so even reading the device's caps
|
||||||
|
via `MEDIA_IOC_DEVICE_INFO` returns `EPERM`.
|
||||||
|
* The broker policy currently only walks `/dev/video*` and only
|
||||||
|
permits devices reporting `V4L2_CAP_VIDEO_M2M{,_MPLANE}`. The
|
||||||
|
matching `/dev/media*` controller node is denied, so when
|
||||||
|
libavcodec's `v4l2_request` hwaccel tries to open it via the
|
||||||
|
broker, `open` returns `EACCES`.
|
||||||
|
|
||||||
|
This patch closes both gaps:
|
||||||
|
|
||||||
|
1. **Broker policy** — after the existing `/dev/video*` walk, now
|
||||||
|
iterates `/dev/media*`. For each, queries
|
||||||
|
`MEDIA_IOC_DEVICE_INFO`; if the reported `info.driver` matches
|
||||||
|
a driver name we already permitted via the M2M video-device
|
||||||
|
pass (i.e. the same driver pair-binds the video node and the
|
||||||
|
media controller), the media node is added to the policy too.
|
||||||
|
Webcams and unrelated media controllers stay denied.
|
||||||
|
|
||||||
|
2. **Seccomp ioctl filter** — adds `'|'` (`kMediaType`) to the
|
||||||
|
RDD allow-list alongside `'V'`, gated on `MOZ_ENABLE_V4L2`.
|
||||||
|
|
||||||
|
Validation
|
||||||
|
----------
|
||||||
|
On RK3399 / Pinebook Pro / mainline kernel (rkvdec stateless H.264
|
||||||
|
decoder via /dev/video1 + /dev/media0), this patch lets the v4l2_request
|
||||||
|
hwaccel open both nodes from a fully-sandboxed RDD process, with no
|
||||||
|
`MOZ_DISABLE_RDD_SANDBOX=1` env override needed. RDD CPU during 1080p30
|
||||||
|
H.264 playback drops from ~78% (software) to ~9.4% (hardware decode),
|
||||||
|
matching the env-disabled-sandbox baseline measured during initial
|
||||||
|
patch development.
|
||||||
|
|
||||||
|
No regression for stateful V4L2 paths (the original /dev/video* loop
|
||||||
|
and 'V' ioctl rule are unchanged); no regression for builds without
|
||||||
|
`MOZ_ENABLE_V4L2` (whole block is `#ifdef`-gated).
|
||||||
|
|
||||||
|
Bug 1969297.
|
||||||
|
|
||||||
|
diff --git a/security/sandbox/linux/SandboxFilter.cpp b/security/sandbox/linux/SandboxFilter.cpp
|
||||||
|
--- a/security/sandbox/linux/SandboxFilter.cpp 2026-04-27 15:33:08.000000000 +0200
|
||||||
|
+++ b/security/sandbox/linux/SandboxFilter.cpp 2026-04-29 14:40:10.984593331 +0200
|
||||||
|
@@ -2064,12 +2064,19 @@
|
||||||
|
static constexpr unsigned long kDmaBufType =
|
||||||
|
static_cast<unsigned long>('b') << _IOC_TYPESHIFT;
|
||||||
|
#ifdef MOZ_ENABLE_V4L2
|
||||||
|
// Type 'V' for V4L2, used for hw accelerated decode
|
||||||
|
static constexpr unsigned long kVideoType =
|
||||||
|
static_cast<unsigned long>('V') << _IOC_TYPESHIFT;
|
||||||
|
+ // firefox-fourier: type '|' is the Media Controller / Request API
|
||||||
|
+ // ioctl family (MEDIA_IOC_DEVICE_INFO, MEDIA_IOC_REQUEST_ALLOC,
|
||||||
|
+ // MEDIA_REQUEST_IOC_QUEUE, ...). Required by libavcodec's
|
||||||
|
+ // v4l2_request hwaccel for stateless decoders (Rockchip rkvdec,
|
||||||
|
+ // hantro on mainline kernel).
|
||||||
|
+ static constexpr unsigned long kMediaType =
|
||||||
|
+ static_cast<unsigned long>('|') << _IOC_TYPESHIFT;
|
||||||
|
#endif
|
||||||
|
// nvidia non-tegra uses some ioctls from this range (but not actual
|
||||||
|
// fbdev ioctls; nvidia uses values >= 200 for the NR field
|
||||||
|
// (low 8 bits))
|
||||||
|
static constexpr unsigned long kFbDevType =
|
||||||
|
static_cast<unsigned long>('F') << _IOC_TYPESHIFT;
|
||||||
|
@@ -2085,12 +2092,13 @@
|
||||||
|
|
||||||
|
// Allow DRI and DMA-Buf for VA-API. Also allow V4L2 if enabled
|
||||||
|
return If(shifted_type == kDrmType, Allow())
|
||||||
|
.ElseIf(shifted_type == kDmaBufType, Allow())
|
||||||
|
#ifdef MOZ_ENABLE_V4L2
|
||||||
|
.ElseIf(shifted_type == kVideoType, Allow())
|
||||||
|
+ .ElseIf(shifted_type == kMediaType, Allow())
|
||||||
|
#endif
|
||||||
|
// NVIDIA decoder from Linux4Tegra, this is specific to Tegra ARM64 SoC
|
||||||
|
#if defined(__aarch64__)
|
||||||
|
.ElseIf(shifted_type == kNvidiaNvmapType, Allow())
|
||||||
|
.ElseIf(shifted_type == kNvidiaNvhostType, Allow())
|
||||||
|
#endif // defined(__aarch64__)
|
||||||
|
diff --git a/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp b/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp
|
||||||
|
--- a/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp 2026-04-27 15:33:09.000000000 +0200
|
||||||
|
+++ b/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp 2026-04-29 14:40:10.984593331 +0200
|
||||||
|
@@ -45,14 +45,16 @@
|
||||||
|
# include "mozilla/WidgetUtilsGtk.h"
|
||||||
|
# include <glib.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef MOZ_ENABLE_V4L2
|
||||||
|
# include <linux/videodev2.h>
|
||||||
|
+# include <linux/media.h>
|
||||||
|
# include <sys/ioctl.h>
|
||||||
|
# include <fcntl.h>
|
||||||
|
+# include <cstring>
|
||||||
|
#endif // MOZ_ENABLE_V4L2
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/sysmacros.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
@@ -870,12 +872,18 @@
|
||||||
|
DIR* dir = opendir("/dev");
|
||||||
|
if (!dir) {
|
||||||
|
SANDBOX_LOG("Couldn't list /dev");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ // Driver names of permitted M2M /dev/video* devices, used below to
|
||||||
|
+ // decide which /dev/media* nodes to permit. firefox-fourier: stateless
|
||||||
|
+ // V4L2 decode (Rockchip rkvdec, hantro) needs the media controller node
|
||||||
|
+ // for the request API (MEDIA_IOC_REQUEST_ALLOC and friends).
|
||||||
|
+ nsTArray<nsCString> permittedDrivers;
|
||||||
|
+
|
||||||
|
struct dirent* dir_entry;
|
||||||
|
while ((dir_entry = readdir(dir))) {
|
||||||
|
if (strncmp(dir_entry->d_name, "video", 5)) {
|
||||||
|
// Not a /dev/video* device, so ignore it
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
@@ -901,20 +909,99 @@
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((cap.device_caps & V4L2_CAP_VIDEO_M2M) ||
|
||||||
|
(cap.device_caps & V4L2_CAP_VIDEO_M2M_MPLANE)) {
|
||||||
|
// This is an M2M device (i.e. not a webcam), so allow access
|
||||||
|
policy->AddPath(rdwr, path.get());
|
||||||
|
+ // Track the driver name so the matching /dev/media* node (if any)
|
||||||
|
+ // can be permitted below.
|
||||||
|
+ const size_t driverLen =
|
||||||
|
+ strnlen(reinterpret_cast<const char*>(cap.driver),
|
||||||
|
+ sizeof(cap.driver));
|
||||||
|
+ nsCString driverName(reinterpret_cast<const char*>(cap.driver),
|
||||||
|
+ driverLen);
|
||||||
|
+ if (!permittedDrivers.Contains(driverName)) {
|
||||||
|
+ permittedDrivers.AppendElement(std::move(driverName));
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ close(fd);
|
||||||
|
+ }
|
||||||
|
+ rewinddir(dir);
|
||||||
|
+
|
||||||
|
+ // firefox-fourier: walk /dev/media* and permit any media controller
|
||||||
|
+ // bound to a driver we already permitted via /dev/video*. Required for
|
||||||
|
+ // V4L2 stateless decode (request API), which queues OUTPUT buffers via
|
||||||
|
+ // ioctls on /dev/media* rather than /dev/video*.
|
||||||
|
+ while ((dir_entry = readdir(dir))) {
|
||||||
|
+ if (strncmp(dir_entry->d_name, "media", 5)) {
|
||||||
|
+ continue;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ nsCString path = "/dev/"_ns;
|
||||||
|
+ path += nsDependentCString(dir_entry->d_name);
|
||||||
|
+
|
||||||
|
+ int fd = open(path.get(), O_RDWR | O_NONBLOCK, 0);
|
||||||
|
+ if (fd < 0) {
|
||||||
|
+ SANDBOX_LOG("Couldn't open media device %s", path.get());
|
||||||
|
+ continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ struct media_device_info info;
|
||||||
|
+ int result = ioctl(fd, MEDIA_IOC_DEVICE_INFO, &info);
|
||||||
|
close(fd);
|
||||||
|
+ if (result < 0) {
|
||||||
|
+ SANDBOX_LOG("Couldn't query media device info for %s", path.get());
|
||||||
|
+ continue;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ const size_t driverLen =
|
||||||
|
+ strnlen(info.driver, sizeof(info.driver));
|
||||||
|
+ nsCString driverName(info.driver, driverLen);
|
||||||
|
+ if (permittedDrivers.Contains(driverName)) {
|
||||||
|
+ policy->AddPath(rdwr, path.get());
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
closedir(dir);
|
||||||
|
|
||||||
|
// FFmpeg V4L2 needs to list /dev to find V4L2 devices.
|
||||||
|
policy->AddPath(rdonly, "/dev");
|
||||||
|
+
|
||||||
|
+ // firefox-fourier: libavcodec's v4l2_request hwaccel uses libudev to
|
||||||
|
+ // enumerate /dev/media* devices for stateless decode.
|
||||||
|
+ // udev_enumerate_scan_devices() iterates ALL /sys/class/* subsystems
|
||||||
|
+ // (drm, dma_heap, ..., not only the ones we care about), opening each
|
||||||
|
+ // subdir to list entries; if any open is denied it returns -EUNATCH
|
||||||
|
+ // and the hwaccel skips device probing entirely. Then for each
|
||||||
|
+ // matching device it follows the /sys/dev/char/MAJOR:MINOR symlink
|
||||||
|
+ // into /sys/devices/platform/* to read attributes. All read-only.
|
||||||
|
+ policy->AddTree(rdonly, "/sys/class");
|
||||||
|
+ policy->AddTree(rdonly, "/sys/devices/platform");
|
||||||
|
+ policy->AddTree(rdonly, "/sys/dev/char");
|
||||||
|
+ policy->AddTree(rdonly, "/sys/bus");
|
||||||
|
+
|
||||||
|
+ // libudev's udev_new() and udev_device_new_from_devnum read from
|
||||||
|
+ // /run/udev/{control,data,tags,...} and /etc/udev/udev.conf. Without
|
||||||
|
+ // these, udev_new() can return NULL or udev_device queries return
|
||||||
|
+ // empty data, breaking the hwaccel's device-translation pass entirely.
|
||||||
|
+ policy->AddTree(rdonly, "/run/udev");
|
||||||
|
+ policy->AddPath(rdonly, "/etc/udev/udev.conf");
|
||||||
|
+ // libudev's udev_new() reads /proc/self/* during initialisation
|
||||||
|
+ // (uid_map, mountinfo, etc.).
|
||||||
|
+ policy->AddTree(rdonly, "/proc/self");
|
||||||
|
+ // libudev / libavcodec call open("/", O_DIRECTORY) during path
|
||||||
|
+ // enumeration (e.g., when resolving /dev/dri/renderD128 via realpath).
|
||||||
|
+ // Listing the root directory is harmless - RDD can already infer the
|
||||||
|
+ // top-level entries from policy paths.
|
||||||
|
+ policy->AddPath(rdonly, "/");
|
||||||
|
+
|
||||||
|
+ // Stateless V4L2 decoders (rkvdec, hantro on mainline) allocate
|
||||||
|
+ // CAPTURE-queue buffers from /dev/dma_heap/*, not internal VPU-private
|
||||||
|
+ // memory. Without rdwr access here, av_hwdevice_ctx_init() succeeds
|
||||||
|
+ // but the first av_buffer_get_ref() fails on the DMA-heap fd.
|
||||||
|
+ policy->AddTree(rdwr, "/dev/dma_heap");
|
||||||
|
}
|
||||||
|
#endif // MOZ_ENABLE_V4L2
|
||||||
|
|
||||||
|
/* static */ UniquePtr<SandboxBroker::Policy>
|
||||||
|
SandboxBrokerPolicyFactory::GetRDDPolicy(int aPid) {
|
||||||
|
auto policy = MakeUnique<SandboxBroker::Policy>();
|
||||||
Reference in New Issue
Block a user