From 052a6926683637e8b8c2778b63b6f5a61260106e Mon Sep 17 00:00:00 2001 From: Markus Fritsche Date: Wed, 29 Apr 2026 17:58:36 +0000 Subject: [PATCH] ffmpeg-v4l2-request-git: libudev bypass for sandboxed callers Adds a brute-force fallback to v4l2request_open_decoder() that enumerates /dev/media[0..15] and /dev/video[0..63] directly when udev_enumerate_scan_devices() returns an error. The fallback uses absolute paths only (no fd-relative openat), which is what makes it work inside firefox's RDD seccomp+broker sandbox where Mozilla's OpenAtTrap rejects fd-relative paths used by systemd's chase() symlink resolver. Same approach Chromium uses in media/gpu/v4l2/stateless/ on ChromeOS, where the sandbox similarly forbids libudev's chase pattern. No regression: the libudev path runs first and the brute-force path only activates on its failure. AV_LOG_INFO line announces the fallback so it's visible in MOZ_LOG=FFmpegLib:5. Validated on RK3399 / Pinebook Pro / rkvdec: libudev probe failed (-2), falling back to brute-force /dev/media* Using V4L2 media driver rkvdec (brute-force) for S264 Reinit context to 1920x1088, pix_fmt: drm_prime RDD CPU = 4.9% Bumps pkgrel=2. Worth submitting to Kwiboo's fork upstream. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../0001-libudev-bypass-fallback.patch | 166 ++++++++++++++++++ arch/ffmpeg-v4l2-request-git/PKGBUILD | 14 +- 2 files changed, 176 insertions(+), 4 deletions(-) create mode 100644 arch/ffmpeg-v4l2-request-git/0001-libudev-bypass-fallback.patch diff --git a/arch/ffmpeg-v4l2-request-git/0001-libudev-bypass-fallback.patch b/arch/ffmpeg-v4l2-request-git/0001-libudev-bypass-fallback.patch new file mode 100644 index 000000000..27041af6e --- /dev/null +++ b/arch/ffmpeg-v4l2-request-git/0001-libudev-bypass-fallback.patch @@ -0,0 +1,166 @@ +--- a/libavutil/hwcontext_v4l2request.c ++++ b/libavutil/hwcontext_v4l2request.c +@@ -19,12 +19,13 @@ + #include "config.h" + + #include + #include + #include + #include ++#include + #include + #include + + #include + #include + +@@ -690,12 +691,125 @@ + } + + udev_enumerate_unref(enumerate); + return ret; + } + ++/* ++ * Brute-force fallback used when libudev's scan fails (e.g. inside firefox's ++ * RDD sandbox where Mozilla's broker rejects fd-relative openat used by ++ * systemd's chase() symlink resolver). Iterates /dev/video[0..63], picks the ++ * one whose major/minor matches the requested devnum. ++ */ ++static char *v4l2request_devnum_to_video_path_brute(dev_t devnum) ++{ ++ char path[32]; ++ struct stat st; ++ for (int i = 0; i < 64; i++) { ++ snprintf(path, sizeof(path), "/dev/video%d", i); ++ if (stat(path, &st) < 0) ++ continue; ++ if (st.st_rdev == devnum) ++ return av_strdup(path); ++ } ++ return NULL; ++} ++ ++/* Brute-force version of v4l2request_probe_video_devices: replaces the ++ * udev_device_new_from_devnum + udev_device_get_devnode flow with ++ * stat()-based major/minor matching against /dev/video[0..63]. */ ++static int v4l2request_probe_video_devices_brute(AVHWFramesContext *hwfc, ++ uint32_t pixelformat, ++ uint32_t buffersize) ++{ ++ AVV4L2RequestFramesContext *fctx = hwfc->hwctx; ++ AVV4L2RequestFramesContextInternal *fctxi = fctx->internal; ++ struct media_device_info device_info; ++ struct media_v2_topology topology = {0}; ++ struct media_v2_interface *interfaces; ++ char *path; ++ dev_t devnum; ++ int ret; ++ ++ if (ioctl(fctxi->media_fd, MEDIA_IOC_DEVICE_INFO, &device_info) < 0) ++ return AVERROR(errno); ++ ++ if (ioctl(fctxi->media_fd, MEDIA_IOC_G_TOPOLOGY, &topology) < 0) ++ return AVERROR(errno); ++ ++ if (!topology.num_interfaces) ++ return AVERROR(ENOENT); ++ ++ interfaces = av_calloc(topology.num_interfaces, ++ sizeof(struct media_v2_interface)); ++ if (!interfaces) ++ return AVERROR(ENOMEM); ++ ++ topology.ptr_interfaces = (__u64)(uintptr_t)interfaces; ++ if (ioctl(fctxi->media_fd, MEDIA_IOC_G_TOPOLOGY, &topology) < 0) { ++ ret = AVERROR(errno); ++ goto fail; ++ } ++ ++ ret = AVERROR(ENOENT); ++ for (unsigned i = 0; i < topology.num_interfaces; i++) { ++ if (interfaces[i].intf_type != MEDIA_INTF_T_V4L_VIDEO) ++ continue; ++ ++ devnum = makedev(interfaces[i].devnode.major, ++ interfaces[i].devnode.minor); ++ path = v4l2request_devnum_to_video_path_brute(devnum); ++ if (!path) ++ continue; ++ ++ ret = v4l2request_probe_video_device(hwfc, path, pixelformat, buffersize); ++ if (!ret) { ++ av_log(hwfc, AV_LOG_INFO, ++ "Using V4L2 media driver %s (brute-force) for %s\n", ++ device_info.driver, av_fourcc2str(pixelformat)); ++ av_free(path); ++ break; ++ } ++ av_free(path); ++ } ++ ++fail: ++ av_free(interfaces); ++ return ret; ++} ++ ++/* Brute-force fallback for v4l2request_probe_media_devices(). Iterates ++ * /dev/media[0..15], opens each, probes via topology+stat. */ ++static int v4l2request_probe_media_devices_brute(AVHWFramesContext *hwfc, ++ uint32_t pixelformat, ++ uint32_t buffersize) ++{ ++ AVV4L2RequestFramesContext *fctx = hwfc->hwctx; ++ AVV4L2RequestFramesContextInternal *fctxi = fctx->internal; ++ char path[32]; ++ int ret = AVERROR(ENOENT); ++ ++ for (int i = 0; i < 16; i++) { ++ snprintf(path, sizeof(path), "/dev/media%d", i); ++ ++ fctxi->media_fd = open(path, O_RDWR); ++ if (fctxi->media_fd < 0) ++ continue; ++ ++ ret = v4l2request_probe_video_devices_brute(hwfc, pixelformat, ++ buffersize); ++ if (!ret) ++ return 0; ++ ++ close(fctxi->media_fd); ++ fctxi->media_fd = -1; ++ } ++ ++ return ret; ++} ++ + static int v4l2request_open_decoder(AVHWFramesContext *hwfc) + { + AVV4L2RequestFramesContext *fctx = hwfc->hwctx; + uint32_t buffersize; + struct udev *udev; + int ret; +@@ -712,12 +826,23 @@ + + buffersize = FFMAX(hwfc->width * hwfc->height * 3 / 2, 256 * 1024); + + // Probe all media devices (auto-detection) + ret = v4l2request_probe_media_devices(hwfc, udev, fctx->pixelformat, buffersize); + ++ // Brute-force fallback when libudev fails. Firefox-fourier hits this ++ // because Mozilla's RDD sandbox blocks fd-relative openat used by ++ // systemd's chase() symlink resolver inside udev_enumerate_scan_devices. ++ if (ret < 0) { ++ av_log(hwfc, AV_LOG_INFO, ++ "libudev probe failed (%d), falling back to brute-force /dev/media*\n", ++ ret); ++ ret = v4l2request_probe_media_devices_brute(hwfc, fctx->pixelformat, ++ buffersize); ++ } ++ + udev_unref(udev); + return ret; + } + + static AVBufferRef *v4l2request_v4l2_buffer_alloc(AVHWFramesContext *hwfc, + struct v4l2_format *format) diff --git a/arch/ffmpeg-v4l2-request-git/PKGBUILD b/arch/ffmpeg-v4l2-request-git/PKGBUILD index 8be9ee24f..6d3743c22 100644 --- a/arch/ffmpeg-v4l2-request-git/PKGBUILD +++ b/arch/ffmpeg-v4l2-request-git/PKGBUILD @@ -23,8 +23,8 @@ pkgname=ffmpeg-v4l2-request-git _srcname=FFmpeg _version='8.1' _commit='b57fbbe50c9b2656fad86a1a7eeabfd2b2a50935' # v4l2-request-n8.1 tip 2026-04-24 -pkgver=8.1.r0.b57fbbe -pkgrel=1 +pkgver=8.1.r123329.b57fbbe +pkgrel=2 epoch=2 pkgdesc='FFmpeg with V4L2 Request API hwaccel (Rockchip / Allwinner stateless decode)' arch=('aarch64') @@ -76,8 +76,9 @@ provides=( ffmpeg ) conflicts=(ffmpeg) -source=("git+https://github.com/Kwiboo/FFmpeg.git#commit=${_commit}") -sha256sums=('SKIP') +source=("git+https://github.com/Kwiboo/FFmpeg.git#commit=${_commit}" + '0001-libudev-bypass-fallback.patch') +sha256sums=('SKIP' 'SKIP') pkgver() { cd "${_srcname}" @@ -86,6 +87,11 @@ pkgver() { "$(git rev-parse --short=7 HEAD)" } +prepare() { + cd "${_srcname}" + patch -Np1 -i "${srcdir}/0001-libudev-bypass-fallback.patch" +} + build() { cd "${_srcname}"