sane-sandboxed: expand symlinks before binding them into the sandbox

This commit is contained in:
Colin 2024-05-12 21:41:49 +00:00
parent 89135d08cb
commit d148b19767

View File

@ -135,6 +135,19 @@ usage() {
## UTILITIES/BOILERPLATE ## 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"` # `normPath outVar "$path"`
# remove duplicate //, reduce '.' and '..' (naively). # remove duplicate //, reduce '.' and '..' (naively).
# expects a full path as input # expects a full path as input
@ -226,6 +239,19 @@ urldecode() {
declare -g "$outVar"="$(echo -e "${i//%/\\x}")" 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 ## HELPERS
@ -769,41 +795,57 @@ maybeAutodetectPaths() {
# for more sophisticated (i.e. complex) backends like firejail, this may break subpaths which were blacklisted earlier. # for more sophisticated (i.e. complex) backends like firejail, this may break subpaths which were blacklisted earlier.
canonicalizePaths() { canonicalizePaths() {
# remove '//' and simplify '.', '..' paths, into canonical absolute logical paths. # remove '//' and simplify '.', '..' paths, into canonical absolute logical paths.
_normPaths=() local normPaths=()
for _path in "${paths[@]}"; do for path in "${paths[@]}"; do
normPath _canonPath "$_path" normPath _canonPath "$path"
_normPaths+=("$_canonPath") normPaths+=("$_canonPath")
done 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. # remove subpaths, but the result might include duplicates.
_toplevelPaths=() local toplevelPaths=()
for _path in "${_normPaths[@]}"; do for _path in "${paths[@]}"; do
_isSubpath= local isSubpath=
for _other in "${_normPaths[@]}"; do for _other in "${paths[@]}"; do
if [[ "$_path" =~ ^$_other/.* ]] || [ "$_other" = "/" ] && [ "$_path" != "/" ]; then if [[ "$_path" =~ ^$_other/.* ]] || [ "$_other" = "/" ] && [ "$_path" != "/" ]; then
# N.B.: $_path lacks a trailing slash, so this never matches self. # N.B.: $_path lacks a trailing slash, so this never matches self.
# UNLESS $_path or $_other is exactly `/`, which we special-case. # UNLESS $_path or $_other is exactly `/`, which we special-case.
_isSubpath=1 isSubpath=1
fi fi
done done
if [ -z "$_isSubpath" ]; then if [ -z "$isSubpath" ]; then
_toplevelPaths+=("$_path") toplevelPaths+=("$_path")
fi fi
done done
# remove duplicated paths. # remove duplicated paths.
canonicalizedPaths=() local canonicalizedPaths=()
for _path in "${_toplevelPaths[@]}"; do for _path in "${toplevelPaths[@]}"; do
_isAlreadyListed= if ! $(contains "$_path" "${canonicalizedPaths[@]}"); then
for _other in "${canonicalizedPaths[@]}"; do
if [ "$_path" = "$_other" ]; then
_isAlreadyListed=1
fi
done
if [ -z "$_isAlreadyListed" ]; then
canonicalizedPaths+=("$_path") canonicalizedPaths+=("$_path")
fi fi
done done
paths=("${canonicalizedPaths[@]}")
} }
@ -826,7 +868,7 @@ parseArgsAndEnvironment() {
### convert generic args into sandbox-specific args ### convert generic args into sandbox-specific args
# order matters: for firejail, early args override the later --profile args # order matters: for firejail, early args override the later --profile args
ingestForBackend() { ingestForBackend() {
for _path in "${canonicalizedPaths[@]}"; do for _path in "${paths[@]}"; do
"$method"IngestPath "$_path" "$method"IngestPath "$_path"
done done
@ -872,6 +914,8 @@ if [ -z "$isDisable" ]; then
"$method"Setup "$method"Setup
maybeAutodetectPaths maybeAutodetectPaths
canonicalizePaths canonicalizePaths
expandLinks
removeSubpaths
ingestForBackend ingestForBackend
"$method"GetCli "$method"GetCli