diff --git a/build.py b/build.py index c55209a82..0e7f74b30 100755 --- a/build.py +++ b/build.py @@ -655,7 +655,21 @@ def build_deb_from_folder(version, binary_folder, want_drm=False): marker = b'/usr/lib/rustdesk/libdrmtap.so.0' binaries = [p for p in glob.glob('tmpdeb/usr/share/rustdesk/lib/librustdesk.so') + glob.glob('tmpdeb/usr/share/rustdesk/rustdesk') if os.path.isfile(p)] - if not any(marker in open(p, 'rb').read() for p in binaries): + def _carries_marker(path): + # Chunked, with an overlap of len(marker)-1 so the marker cannot be missed at a chunk + # boundary: librustdesk.so is ~45 MB and there is no reason to hold it all in memory, + # and the `with` closes deterministically instead of relying on refcounting. + with open(path, 'rb') as f: + tail = b'' + while True: + chunk = f.read(1 << 20) + if not chunk: + return False + if marker in tail + chunk: + return True + tail = chunk[-(len(marker) - 1):] + + if not any(_carries_marker(p) for p in binaries): raise Exception( f'--drm was requested but the staged bundle does not look like a drm build (no ' f'{marker.decode()} dlopen path in {binaries or "any staged binary"}); refusing to ' diff --git a/docs/DRM_CAPTURE_SECURITY.md b/docs/DRM_CAPTURE_SECURITY.md index 8d6592b41..ab0765091 100644 --- a/docs/DRM_CAPTURE_SECURITY.md +++ b/docs/DRM_CAPTURE_SECURITY.md @@ -196,9 +196,11 @@ presents) but reuses RustDesk's own hardened IPC. the capture runs inside the root `--service`, which already holds the capability it needs. Hosts without `/dev/dri` access (or where the library fails to load) transparently fall back to the PipeWire/portal path. -- **Minimum libdrm: 2.4.95 (Ubuntu 18.04 or equivalent).** `libdrmtap` needs the DRM - `GetFB2` framebuffer API (libdrm 2.4.95); Ubuntu 18.04 ships 2.4.101, so every supported - distribution satisfies the API floor. That is an API statement, not a binary-compatibility one: +- **Minimum libdrm: 2.4.95.** `libdrmtap` needs the DRM `GetFB2` framebuffer API, which + landed in libdrm 2.4.95. Ubuntu 18.04 is the oldest distribution worth naming here, and it + straddles the floor: base bionic shipped 2.4.91, below it, while the updates/HWE stack + (2.4.101) is above — so read this as "18.04 with updates, or anything newer", not as + "any 18.04". That is an API statement, not a binary-compatibility one: the `rustdesk-unattended-wayland` deb in this repo's CI is built on an ubuntu-24.04 runner, so the shipped binaries carry that build host's glibc floor. Running on an older distribution means building the deb there (or in a matching container), which the libdrm floor above permits. diff --git a/libs/scrap/src/common/drmtap_dl.rs b/libs/scrap/src/common/drmtap_dl.rs index 2b9d85da3..2b59dfdde 100644 --- a/libs/scrap/src/common/drmtap_dl.rs +++ b/libs/scrap/src/common/drmtap_dl.rs @@ -287,6 +287,15 @@ impl DrmtapLib { let why = if major != DRMTAP_ABI_MAJOR { "the struct layouts this build mirrors track the ABI major, so reading a \ frame descriptor through a mismatched one would mis-decode it" + } else if minor != DRMTAP_ABI_MINOR { + // A NEWER minor lands here too, and telling its user the library "predates the + // split" would send them looking for the wrong problem. Under 0.x semver the + // minor is the breaking axis, and the structs this file mirrors are not frozen, + // so an unverified minor could be read at the wrong offsets. + "this build mirrors the struct layouts of one minor and only that one; \ + under 0.x semver the minor is the breaking axis, so an unverified minor \ + could be read at the wrong offsets. Widening it is a deliberate act, done \ + with the layouts re-checked field by field" } else { "it predates the split-capture API, so its only capture path converts \ in-process, which in the root service means loading the GL stack there" @@ -294,7 +303,7 @@ impl DrmtapLib { let (min_minor, min_patch) = DRMTAP_MIN_MINOR_PATCH; log::warn!( "libdrmtap {name} reports v{major}.{minor}.{patch}, which this build cannot \ - use (needs ABI major {DRMTAP_ABI_MAJOR}, at least \ + use (needs ABI major {DRMTAP_ABI_MAJOR}, minor {DRMTAP_ABI_MINOR}, at least \ v{DRMTAP_ABI_MAJOR}.{min_minor}.{min_patch}): {why}. Refusing to load; \ falling back to PipeWire/portal." );