Add privileged mount helper + polkit policy
POSIX sh helper (pkexec target) that mounts/unmounts CIFS shares with strict validation of the unprivileged caller's arguments: mountpoint confined to $HOME or /run/media/<user>, //host/share source, credfile under the caller's runtime dir at mode 0600, whitelisted SMB version/options/modes. Polkit action authorizes it with auth_admin_keep. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD polkit Policy Configuration 1.0//EN"
|
||||
"http://www.freedesktop.org/software/polkit/policyconfig-1.dtd">
|
||||
<policyconfig>
|
||||
<vendor>Synology NAS plasmoid</vendor>
|
||||
<vendor_url>https://git.houmeres.sk/Houmeres/Plasma-Addon-Synology-NAS</vendor_url>
|
||||
<icon_name>folder-network</icon_name>
|
||||
|
||||
<action id="sk.houmeres.synologynas.manage">
|
||||
<description>Mount or unmount a network share</description>
|
||||
<message>Authentication is required to mount or unmount a network share</message>
|
||||
<defaults>
|
||||
<allow_any>no</allow_any>
|
||||
<allow_inactive>no</allow_inactive>
|
||||
<allow_active>auth_admin_keep</allow_active>
|
||||
</defaults>
|
||||
<annotate key="org.freedesktop.policykit.exec.path">/usr/lib/synology-nas/helper</annotate>
|
||||
<annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
|
||||
</action>
|
||||
</policyconfig>
|
||||
Executable
+118
@@ -0,0 +1,118 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# synology-nas-helper — privileged mount/unmount helper for the Synology NAS
|
||||
# plasmoid. It is invoked as root via pkexec (see the accompanying
|
||||
# sk.houmeres.synologynas.policy). The CALLER IS UNPRIVILEGED, so every argument
|
||||
# is treated as hostile and strictly validated before any mount happens.
|
||||
#
|
||||
# Usage (all arguments positional):
|
||||
# synology-nas-helper mount <source> <mountpoint> <credfile> <vers> <opts> <fmode> <dmode>
|
||||
# synology-nas-helper unmount <mountpoint>
|
||||
#
|
||||
# Guarantees enforced below:
|
||||
# * mountpoint must resolve to a path under the CALLING user's $HOME or
|
||||
# /run/media/<user> (rejects .. traversal via realpath -m).
|
||||
# * source must look like //host/share.
|
||||
# * credfile must live under /run/user/<caller-uid>/, be a regular file,
|
||||
# owned by the caller, mode 0600.
|
||||
# * vers, permission modes and extra options are charset/whitelist checked.
|
||||
#
|
||||
set -eu
|
||||
|
||||
die() { echo "synology-nas-helper: $1" >&2; exit 1; }
|
||||
|
||||
# --- identify the calling (unprivileged) user ---------------------------------
|
||||
[ -n "${PKEXEC_UID:-}" ] || die "must be run through pkexec (PKEXEC_UID unset)"
|
||||
case "$PKEXEC_UID" in
|
||||
''|*[!0-9]*) die "invalid PKEXEC_UID" ;;
|
||||
esac
|
||||
|
||||
pwline=$(getent passwd "$PKEXEC_UID") || die "unknown caller uid $PKEXEC_UID"
|
||||
CALLER_USER=$(printf '%s\n' "$pwline" | cut -d: -f1)
|
||||
CALLER_GID=$(printf '%s\n' "$pwline" | cut -d: -f4)
|
||||
CALLER_HOME=$(printf '%s\n' "$pwline" | cut -d: -f6)
|
||||
[ -n "$CALLER_HOME" ] || die "caller has no home directory"
|
||||
|
||||
# --- shared validators --------------------------------------------------------
|
||||
validate_mountpoint() {
|
||||
# Canonicalise (resolves .. lexically, no need for the path to exist yet).
|
||||
canon=$(realpath -m -- "$1") || die "cannot canonicalise mountpoint"
|
||||
case "$canon" in
|
||||
"$CALLER_HOME"/*) : ;;
|
||||
"/run/media/$CALLER_USER"/*) : ;;
|
||||
*) die "mountpoint must be under \$HOME or /run/media/$CALLER_USER" ;;
|
||||
esac
|
||||
printf '%s' "$canon"
|
||||
}
|
||||
|
||||
# --- subcommands --------------------------------------------------------------
|
||||
do_mount() {
|
||||
[ $# -eq 7 ] || die "mount: expected 7 arguments"
|
||||
source=$1; mountpoint=$2; credfile=$3; vers=$4; extra=$5; fmode=$6; dmode=$7
|
||||
|
||||
case "$source" in
|
||||
//?*/?*) : ;;
|
||||
*) die "source must be //host/share" ;;
|
||||
esac
|
||||
|
||||
mp=$(validate_mountpoint "$mountpoint")
|
||||
|
||||
# Credentials file: under the caller's runtime dir, their file, mode 0600.
|
||||
cf=$(realpath -m -- "$credfile") || die "cannot canonicalise credfile"
|
||||
case "$cf" in
|
||||
"/run/user/$PKEXEC_UID"/*) : ;;
|
||||
*) die "credfile must be under /run/user/$PKEXEC_UID" ;;
|
||||
esac
|
||||
[ -f "$cf" ] || die "credfile not found"
|
||||
[ "$(stat -c %u "$cf")" = "$PKEXEC_UID" ] || die "credfile not owned by caller"
|
||||
[ "$(stat -c %a "$cf")" = "600" ] || die "credfile must be mode 0600"
|
||||
|
||||
case "$vers" in
|
||||
default|1.0|2.0|2.1|3.0|3.02|3.1.1) : ;;
|
||||
*) die "unsupported SMB version: $vers" ;;
|
||||
esac
|
||||
case "$fmode" in 0[0-7][0-7][0-7]) : ;; *) die "bad file mode" ;; esac
|
||||
case "$dmode" in 0[0-7][0-7][0-7]) : ;; *) die "bad dir mode" ;; esac
|
||||
case "$extra" in
|
||||
'') : ;;
|
||||
*[!A-Za-z0-9=,_.:-]*) die "extra mount options contain illegal characters" ;;
|
||||
esac
|
||||
|
||||
if findmnt -rn -o TARGET --mountpoint "$mp" >/dev/null 2>&1; then
|
||||
die "already mounted: $mp"
|
||||
fi
|
||||
|
||||
# Create the mount point and hand the freshly-created dirs to the caller.
|
||||
mkdir -p "$mp" || die "cannot create mount point"
|
||||
chown "$PKEXEC_UID:$CALLER_GID" "$mp" 2>/dev/null || true
|
||||
p=$(dirname "$mp")
|
||||
while [ "$p" != "$CALLER_HOME" ] && [ "$p" != "/" ] && [ "$p" != "/run/media" ]; do
|
||||
chown "$PKEXEC_UID:$CALLER_GID" "$p" 2>/dev/null || true
|
||||
p=$(dirname "$p")
|
||||
done
|
||||
|
||||
opts="credentials=$cf,uid=$PKEXEC_UID,gid=$CALLER_GID,forceuid,forcegid,file_mode=$fmode,dir_mode=$dmode,vers=$vers"
|
||||
[ -n "$extra" ] && opts="$opts,$extra"
|
||||
|
||||
exec mount.cifs "$source" "$mp" -o "$opts"
|
||||
}
|
||||
|
||||
do_unmount() {
|
||||
[ $# -eq 1 ] || die "unmount: expected 1 argument"
|
||||
mp=$(validate_mountpoint "$1")
|
||||
findmnt -rn -t cifs -o TARGET --mountpoint "$mp" >/dev/null 2>&1 \
|
||||
|| die "not a cifs mount: $mp"
|
||||
umount "$mp" || die "umount failed"
|
||||
# Tidy up empty leaf + host dirs (never recursive, never touches files).
|
||||
rmdir "$mp" 2>/dev/null || true
|
||||
rmdir "$(dirname "$mp")" 2>/dev/null || true
|
||||
}
|
||||
|
||||
[ $# -ge 1 ] || die "no subcommand"
|
||||
cmd=$1
|
||||
shift
|
||||
case "$cmd" in
|
||||
mount) do_mount "$@" ;;
|
||||
unmount) do_unmount "$@" ;;
|
||||
*) die "unknown subcommand: $cmd" ;;
|
||||
esac
|
||||
Reference in New Issue
Block a user