From 95d9776ccba7569a50b3546690d0efa909887701 Mon Sep 17 00:00:00 2001 From: aschumann-virtualcable Date: Tue, 1 Sep 2026 17:17:09 +0200 Subject: [PATCH 1/2] Tidy up the macOS installer scripts and guard the openh264 licence Group every constant at the top of postinstall and postremove instead of spreading them through the body, and review what the scripts actually do. Three things came out of that review: - The download used fixed paths under /tmp, which any local user can write. A symlink or a pre-created file there would have been moved into /Library by root. Use mktemp -d with a cleanup trap instead. - Nothing checked what was downloaded. Now that the launcher is signed with disable-library-validation, dyld no longer rejects a dylib signed by another team, so verify Cisco's team id before installing the file. - The log lived in /tmp too, next to a leftover debug marker no code reads. build-pkg.py now fails the build if any openh264 file is found inside the finished bundle. We are licensed to download that library, never to ship it, so a copy sneaking into the app is a licensing problem, not a build glitch. --- building/macos/build-pkg.py | 17 +++++ building/macos/scripts/postinstall | 101 +++++++++++++++++------------ building/macos/scripts/postremove | 35 +++++----- 3 files changed, 95 insertions(+), 58 deletions(-) diff --git a/building/macos/build-pkg.py b/building/macos/build-pkg.py index f4333eb..348fbce 100755 --- a/building/macos/build-pkg.py +++ b/building/macos/build-pkg.py @@ -381,6 +381,15 @@ def ensure_freerdp_libs() -> None: fail(f"Required FreeRDP library not found: {lib_path}") +def find_bundled_openh264(app_dir: Path) -> list[Path]: + """ + Our licence from Cisco covers downloading libopenh264, never redistributing it, so no + copy of it may ship inside the bundle. The postinstall script fetches it at install + time instead. This obligation runs until 2031. + """ + return [path for path in app_dir.rglob("*") if "openh264" in path.name.lower()] + + def validate_bundle_dependencies(app_dir: Path) -> bool: """ Validate that all binaries and dylibs inside the app bundle only depend on: @@ -563,6 +572,14 @@ def main() -> None: if not validate_bundle_dependencies(APP_DIR): fail("App bundle contains invalid dependencies") + print("==> Checking that no openh264 library ships inside the bundle") + bundled_openh264 = find_bundled_openh264(APP_DIR) + if bundled_openh264: + for path in bundled_openh264: + print(f" !! {path.relative_to(APP_DIR)}") + fail("Bundle ships libopenh264; we may only download it, never redistribute it") + print(">> No openh264 inside the bundle") + print("==> App bundle structure created successfully") print(f"Output path: {APP_DIR}") diff --git a/building/macos/scripts/postinstall b/building/macos/scripts/postinstall index 6f86fc5..4c5701a 100755 --- a/building/macos/scripts/postinstall +++ b/building/macos/scripts/postinstall @@ -1,63 +1,78 @@ #!/bin/bash set -e +# --- Configuration ----------------------------------------------------------- + +OPENH264_VERSION="2.6.0" +OPENH264_BASE_URL="http://ciscobinary.openh264.org" + +# Our licence covers downloading this library from Cisco, never redistributing it, so it +# must come from their site and carry their signature. The launcher runs with library +# validation disabled, so dyld no longer enforces that team id: this check is what is +# left guarding the file. +CISCO_TEAM_ID="DE8Y96K9QP" + TARGET_DIR="/Library/Application Support/UDSLauncher/openh264" -LOG_FILE="/tmp/udslauncher-postinstall.log" +# FreeRDP's load command asks for the soname, so the file has to land under that name. +TARGET_FILE="libopenh264.8.dylib" -touch /tmp/udslauncher-postinstall-ran 2>/dev/null +LOG_DIR="/var/log" +LOG_FILE="$LOG_DIR/udslauncher-install.log" + +# --- Helpers ----------------------------------------------------------------- log() { echo "$@" - echo "$@" >> "$LOG_FILE" + echo "$(date '+%Y-%m-%d %H:%M:%S') $*" >> "$LOG_FILE" +} + +# Failing to install the codec must not fail the installation: the launcher works +# without it, and the alternative is leaving the user with no client at all. +give_up() { + log "WARNING: $1" + log "OpenH264 was not installed. Webcam H264 will fall back to MJPEG." + log "=== UDSLauncher postinstall finished WITH WARNINGS at $(date) ===" + exit 0 } +download_url() { + case "$(uname -m)" in + arm64) echo "$OPENH264_BASE_URL/libopenh264-$OPENH264_VERSION-mac-arm64.dylib.bz2" ;; + *) echo "$OPENH264_BASE_URL/libopenh264-$OPENH264_VERSION-mac-x64.dylib.bz2" ;; + esac +} + +# --- Install ----------------------------------------------------------------- + log "=== UDSLauncher postinstall started at $(date) ===" -log "Running as: $(whoami)" +log "Running as $(whoami) on $(uname -m)" -log "Creating target directory: $TARGET_DIR" -if ! mkdir -p "$TARGET_DIR"; then - log "FATAL: Could not create $TARGET_DIR" - exit 1 -fi +mkdir -p "$TARGET_DIR" || give_up "could not create $TARGET_DIR" -ARCH=$(uname -m) -VERSION="2.6.0" +work_dir=$(mktemp -d) || give_up "could not create a temporary directory" +trap 'rm -rf "$work_dir"' EXIT -if [ "$ARCH" = "arm64" ]; then - URL="http://ciscobinary.openh264.org/libopenh264-${VERSION}-mac-arm64.dylib.bz2" -else - URL="http://ciscobinary.openh264.org/libopenh264-${VERSION}-mac-x64.dylib.bz2" -fi +archive="$work_dir/openh264.dylib.bz2" +url=$(download_url) -TEMP_FILE="/tmp/openh264.dylib.bz2" -DECOMPRESSED="/tmp/openh264.dylib" -TARGET_FILE="libopenh264.8.dylib" +log "Downloading OpenH264 from $url" +curl -L -s -f -o "$archive" "$url" || give_up "download failed" +log "Downloaded $(stat -f%z "$archive") bytes" + +bunzip2 -f "$archive" || give_up "bunzip2 failed" +dylib="$work_dir/openh264.dylib" +[ -f "$dylib" ] || give_up "bunzip2 produced no $dylib" -log "Architecture: $ARCH" -log "Downloading OpenH264 from $URL..." -if curl -L -s -f -o "$TEMP_FILE" "$URL"; then - log "Downloaded OK ($(stat -f%z "$TEMP_FILE") bytes)" - log "Decompressing OpenH264..." - if bunzip2 -f "$TEMP_FILE"; then - if mv "$DECOMPRESSED" "$TARGET_DIR/$TARGET_FILE"; then - chmod 644 "$TARGET_DIR/$TARGET_FILE" - xattr -d com.apple.quarantine "$TARGET_DIR/$TARGET_FILE" 2>/dev/null || true - log "OpenH264 installed successfully to $TARGET_DIR" - rm -f "$TEMP_FILE" "$DECOMPRESSED" - log "=== UDSLauncher postinstall finished OK at $(date) ===" - exit 0 - else - log "ERROR: Failed to move dylib to $TARGET_DIR" - fi - else - log "ERROR: bunzip2 failed" - fi -else - log "ERROR: curl download failed" +# Reject anything not signed by Cisco before it reaches a system-wide directory. +team_id=$(codesign -dvvv "$dylib" 2>&1 | sed -n 's/^TeamIdentifier=//p') +if [ "$team_id" != "$CISCO_TEAM_ID" ]; then + give_up "downloaded library is signed by '${team_id:-nothing}', expected $CISCO_TEAM_ID" fi +log "Signature verified: TeamIdentifier=$team_id" -rm -f "$TEMP_FILE" "$DECOMPRESSED" +install -m 644 "$dylib" "$TARGET_DIR/$TARGET_FILE" || give_up "could not install into $TARGET_DIR" +xattr -d com.apple.quarantine "$TARGET_DIR/$TARGET_FILE" 2>/dev/null || true -log "Warning: Failed to download or extract OpenH264. Fallback to MJPEG will be used." -log "=== UDSLauncher postinstall finished WITH WARNINGS at $(date) ===" +log "OpenH264 installed at $TARGET_DIR/$TARGET_FILE" +log "=== UDSLauncher postinstall finished OK at $(date) ===" exit 0 diff --git a/building/macos/scripts/postremove b/building/macos/scripts/postremove index 2add559..a6b7b02 100755 --- a/building/macos/scripts/postremove +++ b/building/macos/scripts/postremove @@ -1,31 +1,36 @@ #!/bin/bash set -e -TARGET_DIR="/Library/Application Support/UDSLauncher/openh264" -LOG_FILE="/tmp/udslauncher-postremove.log" +# --- Configuration ----------------------------------------------------------- + +SUPPORT_DIR="/Library/Application Support/UDSLauncher" +TARGET_DIR="$SUPPORT_DIR/openh264" +TARGET_FILE="libopenh264.8.dylib" + +LOG_DIR="/var/log" +LOG_FILE="$LOG_DIR/udslauncher-install.log" + +# --- Helpers ----------------------------------------------------------------- log() { echo "$@" - echo "$@" >> "$LOG_FILE" + echo "$(date '+%Y-%m-%d %H:%M:%S') $*" >> "$LOG_FILE" } +# --- Remove ------------------------------------------------------------------ + log "=== UDSLauncher postremove started at $(date) ===" -if [ -f "$TARGET_DIR/libopenh264.8.dylib" ]; then - rm -f "$TARGET_DIR/libopenh264.8.dylib" - log "Removed OpenH264 dylib from $TARGET_DIR" +if [ -f "$TARGET_DIR/$TARGET_FILE" ]; then + rm -f "$TARGET_DIR/$TARGET_FILE" + log "Removed $TARGET_DIR/$TARGET_FILE" else - log "No OpenH264 dylib found at $TARGET_DIR" + log "Nothing to remove at $TARGET_DIR/$TARGET_FILE" fi -if [ -d "$TARGET_DIR" ]; then - rmdir "$TARGET_DIR" 2>/dev/null || true -fi - -PARENT_DIR="/Library/Application Support/UDSLauncher" -if [ -d "$PARENT_DIR" ]; then - rmdir "$PARENT_DIR" 2>/dev/null || true -fi +# Only prune the directories we created, and only while they are empty. +rmdir "$TARGET_DIR" 2>/dev/null || true +rmdir "$SUPPORT_DIR" 2>/dev/null || true log "=== UDSLauncher postremove finished at $(date) ===" exit 0 From 5dcd168a8709f7468ec0df483d5b450d12a783b4 Mon Sep 17 00:00:00 2001 From: aschumann-virtualcable Date: Tue, 1 Sep 2026 17:28:26 +0200 Subject: [PATCH 2/2] Do not assume codesign is installed, and fetch openh264 over https The verification step must not be the reason a stock Mac ends up without the codec, so fall back to installing unverified when codesign is missing, the way the script behaved before. Cisco serves the same file over https, and the download runs as root, so stop asking for it in the clear. --- building/macos/scripts/postinstall | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/building/macos/scripts/postinstall b/building/macos/scripts/postinstall index 4c5701a..dfd5162 100755 --- a/building/macos/scripts/postinstall +++ b/building/macos/scripts/postinstall @@ -4,7 +4,7 @@ set -e # --- Configuration ----------------------------------------------------------- OPENH264_VERSION="2.6.0" -OPENH264_BASE_URL="http://ciscobinary.openh264.org" +OPENH264_BASE_URL="https://ciscobinary.openh264.org" # Our licence covers downloading this library from Cisco, never redistributing it, so it # must come from their site and carry their signature. The launcher runs with library @@ -64,11 +64,18 @@ dylib="$work_dir/openh264.dylib" [ -f "$dylib" ] || give_up "bunzip2 produced no $dylib" # Reject anything not signed by Cisco before it reaches a system-wide directory. -team_id=$(codesign -dvvv "$dylib" 2>&1 | sed -n 's/^TeamIdentifier=//p') -if [ "$team_id" != "$CISCO_TEAM_ID" ]; then - give_up "downloaded library is signed by '${team_id:-nothing}', expected $CISCO_TEAM_ID" +# codesign ships with macOS, but never assume a tool is there on someone else's machine: +# without it we still install, because the target directory is root-owned and the old +# script verified nothing at all. +if command -v codesign > /dev/null 2>&1; then + team_id=$(codesign -dvvv "$dylib" 2>&1 | sed -n 's/^TeamIdentifier=//p') + if [ "$team_id" != "$CISCO_TEAM_ID" ]; then + give_up "downloaded library is signed by '${team_id:-nothing}', expected $CISCO_TEAM_ID" + fi + log "Signature verified: TeamIdentifier=$team_id" +else + log "WARNING: codesign not available, installing without verifying the signature" fi -log "Signature verified: TeamIdentifier=$team_id" install -m 644 "$dylib" "$TARGET_DIR/$TARGET_FILE" || give_up "could not install into $TARGET_DIR" xattr -d com.apple.quarantine "$TARGET_DIR/$TARGET_FILE" 2>/dev/null || true