From 82839abea65a2c5e25d43e0856db54ab08d7db25 Mon Sep 17 00:00:00 2001 From: librelad Date: Sat, 23 May 2026 23:22:46 +0100 Subject: [PATCH] harden(desudo): arg-safe runFileOp + convert DNS subsystem off raw sudo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Give dockerCommandRunInstallUser an --argv mode that execs arguments verbatim (sudo -u env ... "$@") instead of bash -c "$*", and point runFileOp at it. The old $*+bash -c re-parse silently mangled backslashes/quotes in args — e.g. sed scripts (\1, \( become 1, ( ) and the sqlite3 .backup arg — so rootless data-plane ops with regex were broken. Verified: the WG_DEFAULT_DNS sed now applies correctly as the install user. All existing runFileOp callers pass plain commands, so the switch is safe (and fixes the latent sqlite3 case). Convert scripts/network/dns/setup_dns.sh: /etc/resolv.conf edits and ping -> runSystem; the WG_DEFAULT_DNS compose-file sed -> runFileOp. Byte-identical in rooted; correct in rootless. Co-Authored-By: Claude Opus 4.7 Signed-off-by: librelad --- scripts/docker/command/docker_run_install.sh | 32 +++++++++++++----- scripts/docker/command/run_privileged.sh | 5 ++- scripts/network/dns/setup_dns.sh | 34 ++++++++++---------- 3 files changed, 43 insertions(+), 28 deletions(-) diff --git a/scripts/docker/command/docker_run_install.sh b/scripts/docker/command/docker_run_install.sh index fb8a69a..10158e1 100755 --- a/scripts/docker/command/docker_run_install.sh +++ b/scripts/docker/command/docker_run_install.sh @@ -14,11 +14,14 @@ dockerCommandRunInstallUser() { local silent_flag="" - if [ "$1" == "--silent" ]; then - silent_flag="$1" - shift - fi - local remote_command="$1" + local argv_mode="" + while true; do + case "$1" in + --silent) silent_flag="1"; shift ;; + --argv) argv_mode="1"; shift ;; + *) break ;; + esac + done local uid uid=$(id -u "$CFG_DOCKER_INSTALL_USER" 2>/dev/null) @@ -36,9 +39,22 @@ dockerCommandRunInstallUser() "PATH=/home/$CFG_DOCKER_INSTALL_USER/bin:/usr/bin:/bin:/usr/local/bin" ) - if [ -n "$silent_flag" ]; then - sudo -u "$CFG_DOCKER_INSTALL_USER" env "${run_env[@]}" bash -c "$remote_command" >/dev/null 2>&1 + # --argv: exec the remaining args verbatim (no shell re-parse) so regex/ + # quotes/backslashes in arguments (e.g. sed scripts) survive intact. Default: + # treat $1 as a shell snippet via bash -c (needed for pipes/redirects/ + # systemctl --user/etc.). + if [ -n "$argv_mode" ]; then + if [ -n "$silent_flag" ]; then + sudo -u "$CFG_DOCKER_INSTALL_USER" env "${run_env[@]}" "$@" >/dev/null 2>&1 + else + sudo -u "$CFG_DOCKER_INSTALL_USER" env "${run_env[@]}" "$@" + fi else - sudo -u "$CFG_DOCKER_INSTALL_USER" env "${run_env[@]}" bash -c "$remote_command" + local remote_command="$1" + if [ -n "$silent_flag" ]; then + sudo -u "$CFG_DOCKER_INSTALL_USER" env "${run_env[@]}" bash -c "$remote_command" >/dev/null 2>&1 + else + sudo -u "$CFG_DOCKER_INSTALL_USER" env "${run_env[@]}" bash -c "$remote_command" + fi fi } diff --git a/scripts/docker/command/run_privileged.sh b/scripts/docker/command/run_privileged.sh index 861ceab..93b6bfb 100644 --- a/scripts/docker/command/run_privileged.sh +++ b/scripts/docker/command/run_privileged.sh @@ -6,8 +6,7 @@ # rooted — app/container files under /docker are root-owned, so ops run via # sudo. This is byte-for-byte the historical behaviour. # rootless — those files are owned by the unprivileged Docker install user, so -# ops run AS that user over the existing SSH channel and need no -# root at all. +# ops run AS that user (via `sudo -u`, no root over the data plane). # Centralising the branch here means each call site is written once and is # correct in both modes, and rooted installs (incl. live boxes) are unchanged. @@ -19,7 +18,7 @@ # this helper is for self-contained commands. runFileOp() { if [[ "$CFG_DOCKER_INSTALL_TYPE" == "rootless" ]]; then - dockerCommandRunInstallUser "$*" + dockerCommandRunInstallUser --argv "$@" else sudo "$@" fi diff --git a/scripts/network/dns/setup_dns.sh b/scripts/network/dns/setup_dns.sh index 3eb05e5..77666f8 100755 --- a/scripts/network/dns/setup_dns.sh +++ b/scripts/network/dns/setup_dns.sh @@ -8,14 +8,14 @@ updateDNS() if [[ "$OS_TYPE" == "Ubuntu" || "$OS_TYPE" == "Debian" ]]; then dnsRemoveNameservers() { - result=$(sudo sed -i '/^nameserver/d' /etc/resolv.conf) + result=$(runSystem sed -i '/^nameserver/d' /etc/resolv.conf) checkSuccess "Removing all instances of Nameserver from Resolv.conf" } if [[ "$flag" == "standalonewireguard" ]]; then dnsRemoveNameservers; - echo "nameserver $CFG_DNS_SERVER_1" | sudo tee -a /etc/resolv.conf > /dev/null - echo "nameserver $CFG_DNS_SERVER_2" | sudo tee -a /etc/resolv.conf > /dev/null + echo "nameserver $CFG_DNS_SERVER_1" | runSystem tee -a /etc/resolv.conf > /dev/null + echo "nameserver $CFG_DNS_SERVER_2" | runSystem tee -a /etc/resolv.conf > /dev/null else # Check if AdGuard is installed local status=$(dockerCheckAppInstalled "adguard" "docker") @@ -23,7 +23,7 @@ updateDNS() setupDNSIP adguard; local adguard_ip="$dns_ip_setup" # Testing Docker IP Address - result=$(sudo ping -c 1 $adguard_ip) + result=$(runSystem ping -c 1 $adguard_ip) if [ $? -eq 0 ]; then isSuccessful "Ping to $adguard_ip was successful." else @@ -31,7 +31,7 @@ updateDNS() isNotice "Defaulting to DNS 1 Server $CFG_DNS_SERVER_1." local adguard_ip="$CFG_DNS_SERVER_1" # Fallback to Quad9 if DNS has issues - result=$(sudo ping -c 1 $adguard_ip) + result=$(runSystem ping -c 1 $adguard_ip) if [ $? -eq 0 ]; then isSuccessful "Ping to $adguard_ip was successful." else @@ -43,7 +43,7 @@ updateDNS() else local adguard_ip="$CFG_DNS_SERVER_1" # Fallback to Quad9 if DNS has issues - result=$(sudo ping -c 1 $adguard_ip) + result=$(runSystem ping -c 1 $adguard_ip) if [ $? -eq 0 ]; then isSuccessful "Ping to $adguard_ip was successful." else @@ -59,7 +59,7 @@ updateDNS() setupDNSIP pihole; local pihole_ip="$dns_ip_setup" # Testing Docker IP Address - result=$(sudo ping -c 1 $pihole_ip) + result=$(runSystem ping -c 1 $pihole_ip) if [ $? -eq 0 ]; then isSuccessful "Ping to $pihole_ip was successful." else @@ -67,7 +67,7 @@ updateDNS() isNotice "Defaulting to DNS 2 Server $CFG_DNS_SERVER_2." local pihole_ip="$CFG_DNS_SERVER_2" # Fallback to Quad9 if DNS has issues - result=$(sudo ping -c 1 $pihole_ip) + result=$(runSystem ping -c 1 $pihole_ip) if [ $? -eq 0 ]; then isSuccessful "Ping to $pihole_ip was successful." else @@ -98,12 +98,12 @@ updateDNS() elif [[ $compose_setup == "app" ]]; then local compose_file="docker-compose.$app_name.yml" fi - result=$(sudo sed -i "s/\(WG_DEFAULT_DNS=\).*/\1$adguard_ip/" $containers_dir$app_name/$compose_file) + result=$(runFileOp sed -i "s/\(WG_DEFAULT_DNS=\).*/\1$adguard_ip/" $containers_dir$app_name/$compose_file) checkSuccess "Updated Wireguard default DNS to $adguard_ip" fi dnsRemoveNameservers; - echo "nameserver $adguard_ip" | sudo tee -a /etc/resolv.conf > /dev/null - echo "nameserver $pihole_ip" | sudo tee -a /etc/resolv.conf > /dev/null + echo "nameserver $adguard_ip" | runSystem tee -a /etc/resolv.conf > /dev/null + echo "nameserver $pihole_ip" | runSystem tee -a /etc/resolv.conf > /dev/null elif [[ "$pihole_ip" == *10.100.0* ]]; then # Wireguard update local status=$(dockerCheckAppInstalled "wireguard" "docker") @@ -114,12 +114,12 @@ updateDNS() elif [[ $compose_setup == "app" ]]; then local compose_file="docker-compose.$app_name.yml" fi - result=$(sudo sed -i "s/\(WG_DEFAULT_DNS=\).*/\1$pihole_ip/" $containers_dir$app_name/$compose_file) + result=$(runFileOp sed -i "s/\(WG_DEFAULT_DNS=\).*/\1$pihole_ip/" $containers_dir$app_name/$compose_file) checkSuccess "Updated Wireguard default DNS to $pihole_ip" fi dnsRemoveNameservers; - echo "nameserver $pihole_ip" | sudo tee -a /etc/resolv.conf > /dev/null - echo "nameserver $adguard_ip" | sudo tee -a /etc/resolv.conf > /dev/null + echo "nameserver $pihole_ip" | runSystem tee -a /etc/resolv.conf > /dev/null + echo "nameserver $adguard_ip" | runSystem tee -a /etc/resolv.conf > /dev/null else # Wireguard update local status=$(dockerCheckAppInstalled "wireguard" "docker") @@ -130,12 +130,12 @@ updateDNS() elif [[ $compose_setup == "app" ]]; then local compose_file="docker-compose.$app_name.yml" fi - result=$(sudo sed -i "s/\(WG_DEFAULT_DNS=\).*/\1$adguard_ip/" $containers_dir$app_name/$compose_file) + result=$(runFileOp sed -i "s/\(WG_DEFAULT_DNS=\).*/\1$adguard_ip/" $containers_dir$app_name/$compose_file) checkSuccess "Updated Wireguard default DNS to $adguard_ip" fi dnsRemoveNameservers; - echo "nameserver $adguard_ip" | sudo tee -a /etc/resolv.conf > /dev/null - echo "nameserver $pihole_ip" | sudo tee -a /etc/resolv.conf > /dev/null + echo "nameserver $adguard_ip" | runSystem tee -a /etc/resolv.conf > /dev/null + echo "nameserver $pihole_ip" | runSystem tee -a /etc/resolv.conf > /dev/null fi if [ "$flag" == "install" ]; then initializeAppVariables $app_name;