From 230a9f486151570142b79d3f9646e36f84d13d8f Mon Sep 17 00:00:00 2001 From: Weston Blieden Date: Fri, 25 Sep 2026 08:49:21 +0200 Subject: [PATCH] Stop executing Tailscale settings as shell code param_bridge wrote params.conf unquoted and the run script sourced it and ran tailscale up through eval, so a setting containing $(...) executed (as root in the ROOT variant). Values are now single-quoted shell literals and the up command is built as an argument list. AdvertiseRoutes also tolerates spaces after commas, which previously dropped every route. Verified on a P3288-LV (OS 12.11). --- common/app/Tailscale_VPN_run | 19 ++++++++++++------- common/app/param_bridge.c | 27 ++++++++++++++++++++------- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/common/app/Tailscale_VPN_run b/common/app/Tailscale_VPN_run index ff7172e..2902aec 100644 --- a/common/app/Tailscale_VPN_run +++ b/common/app/Tailscale_VPN_run @@ -74,24 +74,29 @@ TAILSCALED_PID=$! sleep 2 -TAILSCALE_CMD="$TAILSCALE_PATH --socket=$SOCKET_PATH up --reset --hostname=$(hostname)" +# Arguments for `tailscale up`, built as a list so no setting is ever re-parsed +# by the shell. +set -- --socket="$SOCKET_PATH" up --reset --hostname="$(hostname)" if [ -n "$CUSTOM_SERVER" ]; then - TAILSCALE_CMD="$TAILSCALE_CMD --login-server $CUSTOM_SERVER" + set -- "$@" --login-server "$CUSTOM_SERVER" fi if [ -n "$AUTH_KEY" ]; then - TAILSCALE_CMD="$TAILSCALE_CMD --authkey $AUTH_KEY" + set -- "$@" --authkey "$AUTH_KEY" fi if [ "$ACCEPT_DNS" = "true" ]; then - TAILSCALE_CMD="$TAILSCALE_CMD --accept-dns=true" + set -- "$@" --accept-dns=true fi if [ "$ACCEPT_ROUTES" = "true" ]; then - TAILSCALE_CMD="$TAILSCALE_CMD --accept-routes=true" + set -- "$@" --accept-routes=true fi +# "192.168.1.0/24, 10.0.0.0/24" is a natural way to type the list. +ADVERTISE_ROUTES=$(printf '%s' "$ADVERTISE_ROUTES" | tr -d ' \t\r\n') + # Advertise LAN subnets so this camera acts as a subnet router. Comma-separated # CIDRs (e.g. 192.168.1.0/24,10.0.0.0/8). In userspace-networking mode the # tailscaled netstack forwards tailnet traffic to these subnets, so no kernel IP @@ -103,7 +108,7 @@ if [ -n "$ADVERTISE_ROUTES" ]; then echo 1 >/proc/sys/net/ipv4/ip_forward 2>/dev/null || true echo 1 >/proc/sys/net/ipv6/conf/all/forwarding 2>/dev/null || true fi - TAILSCALE_CMD="$TAILSCALE_CMD --advertise-routes=$ADVERTISE_ROUTES" + set -- "$@" --advertise-routes="$ADVERTISE_ROUTES" fi # Run `tailscale up` in the background and act on its outcome. If the node needs @@ -115,7 +120,7 @@ fi # for it from here, because in POSIX sh `wait` only works on children of the # current shell — a subshell waiting on the parent's child returns 127. { - eval "$TAILSCALE_CMD" + "$TAILSCALE_PATH" "$@" up_exit=$? if [ "$up_exit" -eq 0 ]; then if [ "$VARIANT" = "root" ]; then diff --git a/common/app/param_bridge.c b/common/app/param_bridge.c index e2599b3..1d390a2 100644 --- a/common/app/param_bridge.c +++ b/common/app/param_bridge.c @@ -199,6 +199,19 @@ static void load_config_cache(AXParameter *handle) { #undef LOAD } +/* The run script sources this file, so every value must be a single-quoted + * shell literal or it would be executed. */ +static void write_var(FILE *f, const char *name, const char *value) { + fprintf(f, "%s='", name); + for (const char *p = value; *p; p++) { + if (*p == '\'') + fputs("'\\''", f); + else + fputc(*p, f); + } + fputs("'\n", f); +} + static void write_config_file(void) { FILE *f = fopen(CONFIG_FILE, "w"); if (!f) { @@ -206,15 +219,15 @@ static void write_config_file(void) { CONFIG_FILE, strerror(errno)); return; } - fprintf(f, "CUSTOM_SERVER=%s\n", cache_get(&cfg_custom_server, "")); - fprintf(f, "AUTH_KEY=%s\n", cache_get(&cfg_auth_key, "")); + write_var(f, "CUSTOM_SERVER", cache_get(&cfg_custom_server, "")); + write_var(f, "AUTH_KEY", cache_get(&cfg_auth_key, "")); #ifdef HAS_PROXY_PORTS - fprintf(f, "CONF_HTTP=%s\n", cache_get(&cfg_http_proxy_port, "8080")); - fprintf(f, "CONF_SOCKS=%s\n", cache_get(&cfg_socks5_port, "1080")); + write_var(f, "CONF_HTTP", cache_get(&cfg_http_proxy_port, "8080")); + write_var(f, "CONF_SOCKS", cache_get(&cfg_socks5_port, "1080")); #endif - fprintf(f, "ACCEPT_DNS=%s\n", cache_get(&cfg_accept_dns, "false")); - fprintf(f, "ACCEPT_ROUTES=%s\n", cache_get(&cfg_accept_routes, "false")); - fprintf(f, "ADVERTISE_ROUTES=%s\n", cache_get(&cfg_advertise_routes, "")); + write_var(f, "ACCEPT_DNS", cache_get(&cfg_accept_dns, "false")); + write_var(f, "ACCEPT_ROUTES", cache_get(&cfg_accept_routes, "false")); + write_var(f, "ADVERTISE_ROUTES", cache_get(&cfg_advertise_routes, "")); fclose(f); chmod(CONFIG_FILE, 0600); #ifdef HAS_PROXY_PORTS