119 lines
4.6 KiB
Bash
Executable File
119 lines
4.6 KiB
Bash
Executable File
#!/bin/bash
|
|
set -euo pipefail
|
|
|
|
PACKAGE=${1:?usage: verify-fnos-package.sh package.fpk [expected-version]}
|
|
EXPECTED_VERSION=${2:-}
|
|
VERIFY_TMP_ROOT="${LIVERECORDER_VERIFY_TMPDIR:-${TMPDIR:-/tmp}}"
|
|
MAX_APP_UNCOMPRESSED_BYTES=$((512 * 1024 * 1024))
|
|
mkdir -p "$VERIFY_TMP_ROOT"
|
|
WORK_DIR=$(mktemp -d "${VERIFY_TMP_ROOT%/}/fnos-package-verify.XXXXXX")
|
|
trap 'rm -rf -- "$WORK_DIR"' EXIT
|
|
|
|
manifest_value() {
|
|
sed -n "s/^$1[[:space:]]*=[[:space:]]*//p" "$WORK_DIR/manifest" | head -n 1 | tr -d '\r'
|
|
}
|
|
|
|
tar -xzf "$PACKAGE" -C "$WORK_DIR"
|
|
appname=$(manifest_value appname)
|
|
version=$(manifest_value version)
|
|
test "$appname" = "nxsir.postgresql" || { printf 'unexpected fnOS appname: %s\n' "$appname" >&2; exit 1; }
|
|
test -n "$version"
|
|
if [ -n "$EXPECTED_VERSION" ] && [ "$version" != "$EXPECTED_VERSION" ]; then
|
|
printf 'unexpected fnOS version: expected %s, got %s\n' "$EXPECTED_VERSION" "$version" >&2
|
|
exit 1
|
|
fi
|
|
test "$(manifest_value platform)" = "x86"
|
|
test -x "$WORK_DIR/cmd/main"
|
|
test -x "$WORK_DIR/cmd/install_callback"
|
|
test -s "$WORK_DIR/wizard/install"
|
|
test -s "$WORK_DIR/wizard/upgrade"
|
|
test -s "$WORK_DIR/ICON.PNG"
|
|
test -s "$WORK_DIR/ICON_256.PNG"
|
|
|
|
expected=$(manifest_value checksum)
|
|
actual=$(md5sum "$WORK_DIR/app.tgz" | cut -d' ' -f1)
|
|
test -n "$expected"
|
|
test "$expected" = "$actual"
|
|
gzip -t "$WORK_DIR/app.tgz"
|
|
|
|
gzip -dc "$WORK_DIR/app.tgz" >"$WORK_DIR/app.tar"
|
|
uncompressed_bytes=$(wc -c <"$WORK_DIR/app.tar")
|
|
if [ "$uncompressed_bytes" -gt "$MAX_APP_UNCOMPRESSED_BYTES" ]; then
|
|
printf 'app.tgz expands to %s bytes; limit is %s bytes\n' \
|
|
"$uncompressed_bytes" "$MAX_APP_UNCOMPRESSED_BYTES" >&2
|
|
exit 1
|
|
fi
|
|
|
|
tar -tf "$WORK_DIR/app.tar" >"$WORK_DIR/app-files.txt"
|
|
if awk '/^\// || /(^|\/)\.\.($|\/)/ { unsafe = 1; exit } END { exit unsafe ? 0 : 1 }' "$WORK_DIR/app-files.txt"; then
|
|
printf 'app.tgz contains an unsafe member path\n' >&2
|
|
exit 1
|
|
fi
|
|
|
|
tar -tvf "$WORK_DIR/app.tar" >"$WORK_DIR/app-metadata.txt"
|
|
if awk '
|
|
/^l/ {
|
|
marker = " -> "
|
|
offset = index($0, marker)
|
|
if (offset > 0) {
|
|
target = substr($0, offset + length(marker))
|
|
if (target ~ /^\//) { unsafe = 1; exit }
|
|
}
|
|
}
|
|
/^h/ {
|
|
marker = " link to "
|
|
offset = index($0, marker)
|
|
if (offset > 0) {
|
|
target = substr($0, offset + length(marker))
|
|
if (target ~ /^\//) { unsafe = 1; exit }
|
|
}
|
|
}
|
|
END { exit unsafe ? 0 : 1 }
|
|
' "$WORK_DIR/app-metadata.txt"; then
|
|
printf 'app.tgz contains an unsafe symbolic or hard link\n' >&2
|
|
exit 1
|
|
fi
|
|
|
|
grep -q '^server/wwwroot/index.html$' "$WORK_DIR/app-files.txt"
|
|
grep -q '^runtime/usr/lib/postgresql/15/bin/postgres$' "$WORK_DIR/app-files.txt"
|
|
grep -q '^runtime/usr/lib/postgresql/15/bin/initdb$' "$WORK_DIR/app-files.txt"
|
|
grep -q '^runtime/usr/lib/postgresql/15/bin/pg_ctl$' "$WORK_DIR/app-files.txt"
|
|
grep -q '^runtime/usr/share/postgresql/15/postgresql.conf.sample$' "$WORK_DIR/app-files.txt"
|
|
grep -q '^ui/config$' "$WORK_DIR/app-files.txt"
|
|
grep -q '^ui/images/icon_64.png$' "$WORK_DIR/app-files.txt"
|
|
|
|
grep -q '^server/PostgresService.WebApi$' "$WORK_DIR/app-files.txt"
|
|
grep -q '^runtime/usr/lib/postgresql/15/lib/vector.so$' "$WORK_DIR/app-files.txt"
|
|
grep -q '^runtime/usr/share/postgresql/15/extension/vector.control$' "$WORK_DIR/app-files.txt"
|
|
|
|
if grep -Eq '^runtime/.*/(ffmpeg|ffprobe)$' "$WORK_DIR/app-files.txt"; then
|
|
printf 'ffmpeg and ffprobe must come from the fnOS system environment\n' >&2
|
|
exit 1
|
|
fi
|
|
|
|
if grep -Eq '^runtime/lib/(ld-linux-.*|libc\.so\..*|libBrokenLocale\.so\..*|libanl\.so\..*|libdl\.so\..*|libm(vec)?\.so\..*|libnss_(compat|dns|files|hesiod)\.so\..*|libpthread\.so\..*|libresolv\.so\..*|librt\.so\..*|libthread_db\.so\..*|libutil\.so\..*)$' "$WORK_DIR/app-files.txt"; then
|
|
printf 'the FPK must use the fnOS glibc and matching system loader\n' >&2
|
|
exit 1
|
|
fi
|
|
|
|
if grep -Eq '^(data|records|postgres|log|var)/' "$WORK_DIR/app-files.txt"; then
|
|
printf 'persistent data must not be included in app.tgz\n' >&2
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "$WORK_DIR/app"
|
|
tar -xf "$WORK_DIR/app.tar" -C "$WORK_DIR/app"
|
|
while IFS= read -r -d '' link_path; do
|
|
target=$(readlink -- "$link_path")
|
|
case "$target" in
|
|
/*) printf 'absolute symbolic link in app.tgz: %s -> %s\n' "$link_path" "$target" >&2; exit 1 ;;
|
|
esac
|
|
resolved=$(realpath -m -- "$(dirname -- "$link_path")/$target")
|
|
case "$resolved" in
|
|
"$WORK_DIR/app"/*) ;;
|
|
*) printf 'escaping symbolic link in app.tgz: %s -> %s\n' "$link_path" "$target" >&2; exit 1 ;;
|
|
esac
|
|
done < <(find "$WORK_DIR/app" -type l -print0)
|
|
|
|
printf 'fnOS package verified: %s (%s bytes uncompressed)\n' "$PACKAGE" "$uncompressed_bytes"
|