Compare commits

..
Author SHA1 Message Date
Maksim IvanovandGitHub 0150fa9bc9 Merge pull request #48 from sewi-cpan/fix/tmp-symlink-vuln
Fix predictable /tmp path enabling symlink attack as root
2026-08-17 11:48:57 +03:00
Maksim IvanovandGitHub b22229d589 Merge pull request #49 from FishyW/main
Fix dnf install command in install.sh
2026-08-17 11:48:29 +03:00
WinstonandGitHub 07c8df08c6 Fix dnf install command in install.sh 2026-08-14 11:53:34 +07:00
Sebastian WillingandClaude Sonnet 5 42fe8e0176 Fix predictable /tmp path enabling symlink attack as root
fix-opera.sh runs as root and used a fixed, world-guessable temp
directory (/tmp/opera-fix) created with `mkdir -p`, which succeeds
silently even if the path already exists. A local unprivileged user
could pre-create /tmp/opera-fix as a symlink to a directory they
control and place a malicious libffmpeg.so/libwidevinecdm.so there
ahead of time. When an admin later ran this script, root would copy
the attacker's library into Opera's lib_extra with 0644 perms, where
it gets loaded into every user's browser process on the system.

Switch to `mktemp -d`, which atomically creates a private (0700),
unpredictably-named directory via a bare mkdir() syscall - exclusive
by nature, so it can never adopt a pre-existing path or symlink the
way `mkdir -p` does. Also drop the now-redundant mkdir -p call, since
mktemp already creates the directory.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 08:08:40 +02:00
Maksim IvanovandGitHub a0e9672c45 Merge pull request #47 from mubat/mubat-add-chmod-for-fix-opera-sh
make script executable before executing
2026-08-05 20:38:33 +03:00
Oleh SerhiienkoandGitHub 839f0c06c0 make script executable before executing 2026-07-14 10:20:00 +03:00
Maksim IvanovandGitHub ff1e848f40 Update README.md 2026-07-09 15:23:20 +03:00
Maksim IvanovandGitHub 1cfefbdb35 Merge pull request #45 from alpham8/feature/opensuse-support
feature: openSUSE distribution support expect package manager events
2026-05-30 21:27:20 +03:00
Thomas Wunner 0ca3c8c537 match underlying Chromium version to the compatible ffmpeg and widevine version, repeat this procedure for all installed variants of Opera browser by using temp cache for downloaded assets 2026-05-16 16:22:11 +02:00
Thomas Wunner 3eed381155 Merge branch 'main' into feature/opensuse-support
# Conflicts:
#	install.sh
#	scripts/99fix-opera
#	scripts/fix-opera.sh
2026-05-16 14:49:54 +02:00
Thomas Wunner 86fab5688a openSUSE fixes to install scripts 2026-05-16 14:39:36 +02:00
Thomas Wunner b1726cb9be minor code style and runtime fixes to be more standard-compliant 2025-10-12 08:01:04 +02:00
5 changed files with 194 additions and 40 deletions
+29
View File
@@ -0,0 +1,29 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- version-aware ffmpeg selection: detect Opera's bundled Chromium version and download the matching nwjs-ffmpeg release instead of always using the latest
- per-variant Chromium detection via binary inspection (`get_chromium_major`)
- ffmpeg download caching: skip re-download when multiple Opera variants share the same Chromium version
### Changed
- universal shebang bash interpreter to safely find bash on `$PATH`
- Widevine CDM is now downloaded once before the main loop instead of redundantly per variant
- release data is fetched once upfront (`?per_page=50`) and reused for version matching
### Removed
### Fixed
- in the preflight check: Prefer our user-friendly error messages while avoiding double error message from the operating system by redirecting also `stderr` to `/dev/null`
- potential runtime error: prefer `command -v` over `which`-command, which can have different behaviour on different Linux distributions and always use the first result in `PATH` independently of multiple defined commands
- `grep` error when `CDM_FLAG` starts with `--` by adding explicit end-of-options separator (`grep -qF -- "$CDM_FLAG"`)
+9 -1
View File
@@ -1,3 +1,11 @@
> [!WARNING]
> Make sure, you're using latest version of this script. If not, update it (run installation steps again, say "n" when it asks for creating alias).
>
> Since the may, 16th it doesn't get the latest libffmpeg, but checks for version compatibility.
> [!NOTE]
> All future versions of libffmpeg will be posted in https://github.com/Ld-Hagen/nwjs-ffmpeg-prebuilt/releases instead of this repo.
# Fix Opera Linux libffmpeg & WidevineCdm
* Fixes Opera html5 media content including DRM-protected one.
@@ -32,7 +40,7 @@
3. (*Optional*) Run script. And if it works well go to next step.
`sudo ./scripts/fix-opera.sh`
`chmod +x scripts/fix-opera.sh && sudo ./scripts/fix-opera.sh`
4. Make install.sh executable
Regular → Executable
+17 -2
View File
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
set -euo pipefail
@@ -32,7 +32,7 @@ install_deps() {
apt-get install -y "${DEPS[@]}"
;;
dnf)
dnf install -y "${DEPS[@]} python3-dnf-plugin-post-transaction-actions"
dnf install -y "${DEPS[@]}" python3-dnf-plugin-post-transaction-actions
;;
pacman)
pacman -Sy --needed --noconfirm "${DEPS[@]}"
@@ -84,6 +84,21 @@ if [[ $(uname -m) != "x86_64" ]]; then
exit 1
fi
if ! which unzip > /dev/null; then
printf '\033[1munzip\033[0m package must be installed to run this script\n'
exit 1
fi
if ! which curl > /dev/null; then
printf '\033[1mcurl\033[0m package must be installed to run this script\n'
exit 1
fi
if ! which jq > /dev/null; then
printf '\033[1mjq\033[0m package must be installed to run this script\n'
exit 1
fi
readonly SCRIPT_PATH=$(dirname $(readlink -f $0))
readonly INSTALL_PATH="/root/.scripts"
readonly USER_NAME="${SUDO_USER:-$(logname)}"
+2 -2
View File
@@ -1,2 +1,2 @@
DPkg::Pre-Invoke {"stat -c %Z $(readlink -f $(which opera)) > /tmp/opera.timestamp 2> /dev/null || echo 0 > /tmp/opera.timestamp";};
DPkg::Post-Invoke {"set NEW_OPERA=`stat -c %Z $(readlink -f $(which opera))` || exit 0; set OLD_OPERA=`cat /tmp/opera.timestamp` || exit 0; if [ "$NEW_OPERA" != "$OLD_OPERA" ]; then /root/.scripts/fix-opera.sh; fi; rm -f /tmp/opera.timestamp";};
DPkg::Pre-Invoke {"stat -c %Z $(readlink -f $(command -v opera)) > /tmp/opera.timestamp 2> /dev/null || echo 0 > /tmp/opera.timestamp";};
DPkg::Post-Invoke {"set NEW_OPERA=`stat -c %Z $(readlink -f $(command -v opera))` || exit 0; set OLD_OPERA=`cat /tmp/opera.timestamp` || exit 0; if [ "$NEW_OPERA" != "$OLD_OPERA" ]; then /root/.scripts/fix-opera.sh; fi; rm -f /tmp/opera.timestamp";};
Regular → Executable
+135 -33
View File
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
if [[ $(whoami) != "root" ]]; then
printf 'Try to run it with sudo\n'
@@ -10,17 +10,17 @@ if [[ $(uname -m) != "x86_64" ]]; then
exit 1
fi
if ! command -v unzip > /dev/null; then
if ! command -v unzip 2>&1 /dev/null; then
printf '\033[1munzip\033[0m package must be installed to run this script\n'
exit 1
fi
if ! command -v curl > /dev/null; then
if ! command -v curl 2>&1 /dev/null; then
printf '\033[1mcurl\033[0m package must be installed to run this script\n'
exit 1
fi
if ! command -v jq > /dev/null; then
if ! command -v jq 2>&1 /dev/null; then
printf '\033[1mjq\033[0m package must be installed to run this script\n'
exit 1
fi
@@ -31,7 +31,12 @@ fi
#Config section
readonly FIX_WIDEVINE=true
readonly FIX_DIR='/tmp/opera-fix'
FIX_DIR=$(mktemp -d -t opera-fix.XXXXXXXX)
if [[ ! -d "$FIX_DIR" ]]; then
printf 'Failed to create a secure temporary directory\n'
exit 1
fi
readonly FIX_DIR
readonly FFMPEG_SRC_MAIN='https://api.github.com/repos/Ld-Hagen/nwjs-ffmpeg-prebuilt/releases'
readonly FFMPEG_SRC_ALT='https://api.github.com/repos/Ld-Hagen/fix-opera-linux-ffmpeg-widevine/releases'
readonly WIDEVINE_SRC='https://raw.githubusercontent.com/mozilla-firefox/firefox/refs/heads/main/toolkit/content/gmp-sources/widevinecdm.json'
@@ -53,60 +58,140 @@ if [ -x "$(command -v opera-gx)" ]; then
OPERA_VERSIONS+=("opera-gx")
fi
get_chromium_major() {
local opera_dir="$1"
local opera_name="$2"
local version=""
for bin in "$opera_dir/$opera_name" "$opera_dir/opera"; do
if [[ -f "$bin" ]]; then
version=$(grep -ao 'Chrome/[0-9]\+' "$bin" 2>/dev/null | head -1 | grep -o '[0-9]\+')
if [[ -n "$version" ]]; then
echo "$version"
return 0
fi
fi
done
return 1
}
get_ffmpeg_url_for_chromium() {
local chromium_major="$1"
local best_tag=""
local best_url=""
for releases_json in "$RELEASES_MAIN" "$RELEASES_ALT"; do
local match
match=$(echo "$releases_json" | jq -r --arg ver "$chromium_major" '
[.[] | select((.body // "") | test("Chromium " + $ver + "\\."))]
| sort_by(.published_at)
| if length > 0 then .[-1] | "\(.tag_name)\t\(.assets[0].browser_download_url)" else empty end
')
if [[ -n "$match" ]]; then
local tag url
tag=$(echo "$match" | cut -f1)
url=$(echo "$match" | cut -f2)
if [[ -z "$best_tag" || "$tag" > "$best_tag" ]]; then
best_tag="$tag"
best_url="$url"
fi
fi
done
if [[ -n "$best_url" ]]; then
printf '%s\t%s' "$best_tag" "$best_url"
return 0
fi
return 1
}
get_latest_ffmpeg_url() {
local url_main url_alt
url_main=$(echo "$RELEASES_MAIN" | jq -rS 'sort_by(.published_at) | .[-1].assets[0].browser_download_url')
url_alt=$(echo "$RELEASES_ALT" | jq -rS 'sort_by(.published_at) | .[-1].assets[0].browser_download_url')
if [[ $(basename "$url_alt") < $(basename "$url_main") ]]; then
echo "$url_main"
else
echo "$url_alt"
fi
}
#Getting download links
printf 'Getting download links...\n'
##ffmpeg
readonly FFMPEG_URL_MAIN=$(curl -sL4 $FFMPEG_SRC_MAIN | jq -rS 'sort_by(.published_at) | .[-1].assets[0].browser_download_url')
readonly FFMPEG_URL_ALT=$(curl -sL4 $FFMPEG_SRC_ALT | jq -rS 'sort_by(.published_at) | .[-1].assets[0].browser_download_url')
[[ $(basename $FFMPEG_URL_ALT) < $(basename $FFMPEG_URL_MAIN) ]] && readonly FFMPEG_URL=$FFMPEG_URL_MAIN || readonly FFMPEG_URL=$FFMPEG_URL_ALT
if [[ -z $FFMPEG_URL ]]; then
printf 'Failed to get ffmpeg download URL. Exiting...\n'
exit 1
fi
##Fetch all release data once
RELEASES_MAIN=$(curl -sL4 "$FFMPEG_SRC_MAIN?per_page=50")
RELEASES_ALT=$(curl -sL4 "$FFMPEG_SRC_ALT?per_page=50")
##Widevine
if $FIX_WIDEVINE; then
readonly WIDEVINE_URL=$(curl -sL4 $WIDEVINE_SRC | jq -r '.vendors."gmp-widevinecdm".platforms."Linux_x86_64-gcc3".mirrorUrls[0]')
fi
#Downloading files
printf 'Downloading files...\n'
mkdir -p "$FIX_DIR"
##ffmpeg
curl -L4 --progress-bar $FFMPEG_URL -o "$FIX_DIR/ffmpeg.zip"
if [ $? -ne 0 ]; then
printf 'Failed to download ffmpeg. Check your internet connection or try later\n'
exit 1
fi
##Widevine
#Downloading Widevine
if $FIX_WIDEVINE; then
printf 'Downloading Widevine CDM...\n'
echo -e "From URL: $WIDEVINE_URL\n"
curl -L4 --progress-bar "$WIDEVINE_URL" -o "$FIX_DIR/widevine.zip"
if [ $? -ne 0 ]; then
printf 'Failed to download Widevine CDM. Check your internet connection or try later\n'
exit 1
fi
echo "Extracting WidevineCDM..."
unzip -oj "$FIX_DIR/widevine.zip" -d "$FIX_DIR" > /dev/null 2>/dev/null
fi
#Extracting files
##ffmpeg
echo "Extracting ffmpeg..."
unzip -o "$FIX_DIR/ffmpeg.zip" -d $FIX_DIR > /dev/null
##Widevine
if $FIX_WIDEVINE; then
echo "Extracting WidevineCDM..."
unzip -oj "$FIX_DIR/widevine.zip" -d $FIX_DIR > /dev/null 2>/dev/null
fi
LAST_FFMPEG_TAG=""
for opera in ${OPERA_VERSIONS[@]}; do
echo "Doing $opera"
EXECUTABLE=$(command -v "$opera")
if [[ "$ARCH_SYSTEM" == true ]]; then
OPERA_DIR=$(dirname $(cat $EXECUTABLE | grep exec | cut -d ' ' -f 2))
elif head -1 "$EXECUTABLE" | grep -q 'bash\|sh'; then
# The executable is a shell wrapper (e.g. openSUSE packages Opera as a wrapper
# script). readlink -f on a script returns the script itself, not the real binary,
# so we parse the LIBDIR variable directly from the wrapper instead.
PARSED_LIBDIR=$(grep '^LIBDIR=' "$EXECUTABLE" | head -1 | sed 's/LIBDIR=//;s/"//g' | sed "s/\\\$PROGNAME/$opera/g")
if [[ -n "$PARSED_LIBDIR" && -d "$PARSED_LIBDIR" ]]; then
OPERA_DIR="$PARSED_LIBDIR"
else
OPERA_DIR=$(dirname $(readlink -f $EXECUTABLE))
fi
else
OPERA_DIR=$(dirname $(readlink -f $EXECUTABLE))
fi
CHROMIUM_MAJOR=$(get_chromium_major "$OPERA_DIR" "$opera")
if [[ -n "$CHROMIUM_MAJOR" ]]; then
printf 'Detected Chromium %s for %s\n' "$CHROMIUM_MAJOR" "$opera"
FFMPEG_MATCH=$(get_ffmpeg_url_for_chromium "$CHROMIUM_MAJOR")
if [[ -n "$FFMPEG_MATCH" ]]; then
FFMPEG_TAG=$(echo "$FFMPEG_MATCH" | cut -f1)
FFMPEG_URL=$(echo "$FFMPEG_MATCH" | cut -f2)
else
printf 'WARNING: No ffmpeg release found for Chromium %s, using latest\n' "$CHROMIUM_MAJOR"
FFMPEG_TAG="latest"
FFMPEG_URL=$(get_latest_ffmpeg_url)
fi
else
printf 'WARNING: Could not detect Chromium version for %s, using latest ffmpeg\n' "$opera"
FFMPEG_TAG="latest"
FFMPEG_URL=$(get_latest_ffmpeg_url)
fi
if [[ "$FFMPEG_TAG" != "$LAST_FFMPEG_TAG" ]]; then
printf 'Downloading ffmpeg (%s)...\n' "$FFMPEG_TAG"
echo -e "From: $FFMPEG_URL\n"
curl -L4 --progress-bar "$FFMPEG_URL" -o "$FIX_DIR/ffmpeg.zip"
if [ $? -ne 0 ]; then
printf 'Failed to download ffmpeg. Check your internet connection or try later\n'
exit 1
fi
echo "Extracting ffmpeg..."
unzip -o "$FIX_DIR/ffmpeg.zip" -d "$FIX_DIR" > /dev/null
LAST_FFMPEG_TAG="$FFMPEG_TAG"
else
printf 'Using cached ffmpeg (%s)\n' "$FFMPEG_TAG"
fi
OPERA_LIB_DIR="$OPERA_DIR/lib_extra"
OPERA_WIDEVINE_DIR="$OPERA_LIB_DIR/WidevineCdm"
OPERA_WIDEVINE_SO_DIR="$OPERA_WIDEVINE_DIR/_platform_specific/linux_x64"
@@ -135,6 +220,23 @@ for opera in ${OPERA_VERSIONS[@]}; do
cp -f "$FIX_DIR/$WIDEVINE_MANIFEST_NAME" "$OPERA_WIDEVINE_DIR"
chmod 0644 "$OPERA_WIDEVINE_DIR/$WIDEVINE_MANIFEST_NAME"
printf "[\n {\n \"preload\": \"$OPERA_WIDEVINE_DIR\"\n }\n]\n" > "$OPERA_WIDEVINE_CONFIG"
# Newer Chromium-based Opera versions (110+) no longer read widevine_config.json.
# On zypper-based systems (openSUSE) Opera reads OPERA_FLAGS from /etc/default/opera,
# so we inject --widevine-cdm-path there as well to cover both loading mechanisms.
if command -v zypper &>/dev/null && [[ -f /etc/default/$opera ]]; then
OPERA_DEFAULT="/etc/default/$opera"
CDM_FLAG="--widevine-cdm-path=$OPERA_WIDEVINE_DIR"
if grep -q 'OPERA_FLAGS=' "$OPERA_DEFAULT"; then
# Append the flag if not already present
if ! grep -qF -- "$CDM_FLAG" "$OPERA_DEFAULT"; then
sed -i "s|OPERA_FLAGS=\"\(.*\)\"|OPERA_FLAGS=\"\1 $CDM_FLAG\"|" "$OPERA_DEFAULT"
fi
else
echo "OPERA_FLAGS=\"$CDM_FLAG\"" >> "$OPERA_DEFAULT"
fi
printf "Widevine CDM path added to %s\n" "$OPERA_DEFAULT"
fi
fi
done