1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
|
#!/usr/bin/bats -t
# SPDX-License-Identifier: MPL-2.0
#
# libpathrs: safe path resolution on Linux
# Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
# Copyright (C) 2019-2025 SUSE LLC
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
load helpers
function setup() {
setup_tmpdirs
}
function teardown() {
teardown_tmpdirs
}
# TODO: All of these tests are very limited because we cannot verify anything
# useful about the opened files (and the path is actually not guaranteed to be
# correct outside of magic-link cases). Ideally we would instead output
# something simple like the fdinfo, stat, and/or contents to verify against.
@test "procfs --base open --oflags O_RDONLY" {
pathrs-cmd procfs open modules
[ "$status" -eq 0 ]
grep -E '^FILE-PATH (/proc)?/modules$' <<<"$output"
pathrs-cmd procfs --base root open --oflags O_RDONLY modules
[ "$status" -eq 0 ]
grep -E '^FILE-PATH (/proc)?/modules$' <<<"$output"
}
@test "procfs --base pid=\$\$ --oflags O_RDONLY" {
pathrs-cmd procfs open modules
[ "$status" -eq 0 ]
grep -E '^FILE-PATH (/proc)?/modules$' <<<"$output"
pathrs-cmd procfs --base root open --oflags O_RDONLY modules
[ "$status" -eq 0 ]
grep -E '^FILE-PATH (/proc)?/modules$' <<<"$output"
}
@test "procfs --base self --oflags O_RDONLY" {
pathrs-cmd procfs --base self open status
[ "$status" -eq 0 ]
grep -E '^FILE-PATH (/proc)?/[0-9]+/status$' <<<"$output"
pathrs-cmd procfs --base self open stack
[ "$status" -eq 0 ]
# NOTE: While only admins can read from /proc/$n/stack, we can open it.
grep -E '^FILE-PATH (/proc)?/[0-9]+/stack$' <<<"$output"
}
@test "procfs --base thread-self --oflags O_RDONLY" {
pathrs-cmd procfs --base thread-self open status
[ "$status" -eq 0 ]
grep -E '^FILE-PATH (/proc)?/[0-9]+/task/[0-9]+/status$' <<<"$output"
pathrs-cmd procfs --base thread-self open stack
[ "$status" -eq 0 ]
# NOTE: While only admins can read from /proc/$n/stack, we can open it.
grep -E '^FILE-PATH (/proc)?/[0-9]+/task/[0-9]+/stack$' <<<"$output"
}
# Make sure that thread-self and self are actually handled differently.
@test "procfs open [self != thread-self]" {
pathrs-cmd procfs --base self open task
[ "$status" -eq 0 ]
grep -E '^FILE-PATH (/proc)?/[0-9]*/task$' <<<"$output"
pathrs-cmd procfs --base thread-self open task
check-errno ENOENT
}
@test "procfs open --follow [symlinks]" {
pathrs-cmd procfs open mounts
[ "$status" -eq 0 ]
grep -E '^FILE-PATH (/proc)?/[0-9]+/mounts$' <<<"$output"
pathrs-cmd procfs open --oflags O_DIRECTORY mounts
check-errno ENOTDIR
pathrs-cmd procfs open --oflags O_DIRECTORY net
[ "$status" -eq 0 ]
grep -E '^FILE-PATH (/proc)?/[0-9]+/net$' <<<"$output"
pathrs-cmd procfs open --follow self
[ "$status" -eq 0 ]
grep -E '^FILE-PATH (/proc)?/[0-9]+$' <<<"$output"
pathrs-cmd procfs open --follow thread-self
if [ -e /proc/thread-self ]; then
[ "$status" -eq 0 ]
grep -E '^FILE-PATH (/proc)?/[0-9]+/task/[0-9]+$' <<<"$output"
else
check-errno ENOENT
fi
}
@test "procfs open --follow [magic-links]" {
exepath="$(readlink -f "$PATHRS_CMD")"
pathrs-cmd procfs --base self open --follow --oflags O_RDONLY exe
[ "$status" -eq 0 ]
! grep -E '^FILE-PATH (/proc)?/[0-9]+/.*$' <<<"$output"
# We can only guess /proc/self/exe for compiled pathrs-cmd binaries.
if is-compiled "$PATHRS_CMD"; then
grep -Fx "FILE-PATH $exepath" <<<"$output"
fi
realpwd="$(readlink -f "$PWD")"
pathrs-cmd procfs --base self open --follow --oflags O_RDONLY cwd
[ "$status" -eq 0 ]
grep -Fx "FILE-PATH $realpwd" <<<"$output"
dummyfile="$(setup_tmpdir)/dummyfile"
echo "THIS SHOULD BE TRUNCATED" >"$dummyfile"
[ "$(stat -c '%s' "$dummyfile")" -ne 0 ]
pathrs-cmd procfs --base thread-self open --follow --oflags O_RDWR,O_TRUNC fd/100 100>>"$dummyfile"
[ "$status" -eq 0 ]
grep -Fx "FILE-PATH $dummyfile" <<<"$output"
# The file should've been truncated by O_TRUNC.
[ "$(stat -c '%s' "$dummyfile")" -eq 0 ]
}
@test "procfs open --no-follow [symlinks]" {
pathrs-cmd procfs open --no-follow mounts
check-errno ELOOP
pathrs-cmd procfs open --no-follow net
check-errno ELOOP
pathrs-cmd procfs open --no-follow self
check-errno ELOOP
pathrs-cmd procfs open --no-follow thread-self
if [ -e /proc/thread-self ]; then
check-errno ELOOP
else
check-errno ENOENT
fi
}
@test "procfs open --no-follow [magic-links]" {
pathrs-cmd procfs --base pid=1 open --no-follow --oflags O_DIRECTORY root
# O_DIRECTORY beats O_NOFOLLOW!
check-errno ENOTDIR
pathrs-cmd procfs --base pid=1 open --no-follow --oflags O_RDWR root
# O_NOFOLLOW beats permission errors
check-errno ELOOP
pathrs-cmd procfs --base self open --no-follow --oflags O_RDONLY exe
check-errno ELOOP
pathrs-cmd procfs --base thread-self open --no-follow --oflags O_RDONLY exe
check-errno ELOOP
dummyfile="$(setup_tmpdir)/dummyfile"
echo "THIS SHOULD NOT BE TRUNCATED" >"$dummyfile"
[ "$(stat -c '%s' "$dummyfile")" -ne 0 ]
pathrs-cmd procfs --base thread-self open --no-follow --oflags O_RDWR,O_TRUNC fd/100 100>>"$dummyfile"
check-errno ELOOP
# The file should NOT have been truncated by O_TRUNC.
[ "$(stat -c '%s' "$dummyfile")" -ne 0 ]
}
@test "procfs open --no-follow --oflags O_PATH [symlinks]" {
pathrs-cmd procfs --base self open --no-follow --oflags O_PATH exe
[ "$status" -eq 0 ]
grep -E '^FILE-PATH (/proc)?/[0-9]+/exe$' <<<"$output"
pathrs-cmd procfs --base pid=$$ open --oflags O_PATH,O_NOFOLLOW exe
[ "$status" -eq 0 ]
grep -E '^FILE-PATH (/proc)?/'"$$"'/exe$' <<<"$output"
pathrs-cmd procfs --base thread-self open --oflags O_PATH,O_NOFOLLOW exe
[ "$status" -eq 0 ]
grep -E '^FILE-PATH (/proc)?/[0-9]+/task/[0-9]+/exe$' <<<"$output"
}
@test "procfs open [symlink parent component]" {
pathrs-cmd procfs --base root open self/status
[ "$status" -eq 0 ]
grep -E '^FILE-PATH (/proc)?/[0-9]+/status$' <<<"$output"
pathrs-cmd procfs --base root open self/fdinfo/0
[ "$status" -eq 0 ]
grep -E '^FILE-PATH (/proc)?/[0-9]+/fdinfo/0$' <<<"$output"
exepath="$(readlink -f "$PATHRS_CMD")"
pathrs-cmd procfs --base root open self/exe
[ "$status" -eq 0 ]
! grep -E '^FILE-PATH (/proc)?/[0-9]+/.*$' <<<"$output"
# We can only guess /proc/self/exe for compiled pathrs-cmd binaries.
if is-compiled "$PATHRS_CMD"; then
grep -Fx "FILE-PATH $exepath" <<<"$output"
fi
}
|