From d148b197673d4f03adc02a8925b3dbe054b1adcd Mon Sep 17 00:00:00 2001 From: Colin Date: Sun, 12 May 2024 21:41:49 +0000 Subject: [PATCH] sane-sandboxed: expand symlinks before binding them into the sandbox --- pkgs/additional/sane-sandboxed/sane-sandboxed | 88 ++++++++++++++----- 1 file changed, 66 insertions(+), 22 deletions(-) diff --git a/pkgs/additional/sane-sandboxed/sane-sandboxed b/pkgs/additional/sane-sandboxed/sane-sandboxed index 26af48f7..d196e935 100644 --- a/pkgs/additional/sane-sandboxed/sane-sandboxed +++ b/pkgs/additional/sane-sandboxed/sane-sandboxed @@ -135,6 +135,19 @@ usage() { ## UTILITIES/BOILERPLATE +# `relativeTo base-dir path` +# if `path` is absolute, returns `path` +# otherwise, joins `path` onto `base-dir` +relativeTo() { + local base="$1" + local path="$2" + if [ "${path:0:1}" == "/" ]; then + echo "$path" + else + echo "$base/$path" + fi +} + # `normPath outVar "$path"` # remove duplicate //, reduce '.' and '..' (naively). # expects a full path as input @@ -226,6 +239,19 @@ urldecode() { declare -g "$outVar"="$(echo -e "${i//%/\\x}")" } +# `contains needle ${haystack[0]} ${haystack[1]} ...` +# evals `true` if the first argument is equal to any of the other args +contains() { + local needle="$1" + shift + for item in "$@"; do + if [ "$needle" = "$item" ]; then + return 0 + fi + done + return 1 +} + ## HELPERS @@ -769,41 +795,57 @@ maybeAutodetectPaths() { # for more sophisticated (i.e. complex) backends like firejail, this may break subpaths which were blacklisted earlier. canonicalizePaths() { # remove '//' and simplify '.', '..' paths, into canonical absolute logical paths. - _normPaths=() - for _path in "${paths[@]}"; do - normPath _canonPath "$_path" - _normPaths+=("$_canonPath") + local normPaths=() + for path in "${paths[@]}"; do + normPath _canonPath "$path" + normPaths+=("$_canonPath") done - + paths=("${normPaths[@]}") +} +expandLink() { + local target="$(readlink "$1")" + if [ -n "$target" ]; then + # make absolute + target="$(relativeTo "$(dirname "$1")" "$target")" + # add + expand the symlink further, but take care to avoid infinite recursion + normPath _canonTarget "$target" + if ! $(contains "$_canonTarget" "${paths[@]}"); then + paths+=("$_canonTarget") + expandLink "$_canonTarget" + fi + fi +} +### expand `paths` until it contains no symlinks whose target isn't also in `paths` +expandLinks() { + for _path in "${paths[@]}"; do + expandLink "$_path" + done +} +removeSubpaths() { # remove subpaths, but the result might include duplicates. - _toplevelPaths=() - for _path in "${_normPaths[@]}"; do - _isSubpath= - for _other in "${_normPaths[@]}"; do + local toplevelPaths=() + for _path in "${paths[@]}"; do + local isSubpath= + for _other in "${paths[@]}"; do if [[ "$_path" =~ ^$_other/.* ]] || [ "$_other" = "/" ] && [ "$_path" != "/" ]; then # N.B.: $_path lacks a trailing slash, so this never matches self. # UNLESS $_path or $_other is exactly `/`, which we special-case. - _isSubpath=1 + isSubpath=1 fi done - if [ -z "$_isSubpath" ]; then - _toplevelPaths+=("$_path") + if [ -z "$isSubpath" ]; then + toplevelPaths+=("$_path") fi done # remove duplicated paths. - canonicalizedPaths=() - for _path in "${_toplevelPaths[@]}"; do - _isAlreadyListed= - for _other in "${canonicalizedPaths[@]}"; do - if [ "$_path" = "$_other" ]; then - _isAlreadyListed=1 - fi - done - if [ -z "$_isAlreadyListed" ]; then + local canonicalizedPaths=() + for _path in "${toplevelPaths[@]}"; do + if ! $(contains "$_path" "${canonicalizedPaths[@]}"); then canonicalizedPaths+=("$_path") fi done + paths=("${canonicalizedPaths[@]}") } @@ -826,7 +868,7 @@ parseArgsAndEnvironment() { ### convert generic args into sandbox-specific args # order matters: for firejail, early args override the later --profile args ingestForBackend() { - for _path in "${canonicalizedPaths[@]}"; do + for _path in "${paths[@]}"; do "$method"IngestPath "$_path" done @@ -872,6 +914,8 @@ if [ -z "$isDisable" ]; then "$method"Setup maybeAutodetectPaths canonicalizePaths + expandLinks + removeSubpaths ingestForBackend "$method"GetCli