Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8844f806a | ||
|
|
33eb90454d | ||
|
|
ebb9ab3aa1 | ||
|
|
f6bbb95eee | ||
|
|
4741d34783 | ||
|
|
a6e330827e | ||
|
|
6fd78e0515 | ||
|
|
cb60cf0218 | ||
|
|
af87e67a87 | ||
|
|
7a8e5c5fbd | ||
|
|
65bc5bd427 | ||
|
|
69fc26bfec | ||
|
|
7687cfa577 | ||
|
|
d8668e6d64 | ||
|
|
8cca841258 | ||
|
|
cb794e100c | ||
|
|
6d9f740a2b | ||
|
|
7821523942 | ||
|
|
0fb13c2b49 | ||
|
|
fcce1b5a4a | ||
|
|
c27e3d5528 | ||
|
|
353b649bec | ||
|
|
70f5233888 | ||
|
|
cdc08870de |
52 changed files with 2150 additions and 417 deletions
11
.github/workflows/ci.yml
vendored
11
.github/workflows/ci.yml
vendored
|
|
@ -74,6 +74,7 @@ jobs:
|
||||||
tests/test_block_type_confusion.sh \
|
tests/test_block_type_confusion.sh \
|
||||||
tests/test_disk_device_capacity.sh \
|
tests/test_disk_device_capacity.sh \
|
||||||
tests/test_f09_preface.sh \
|
tests/test_f09_preface.sh \
|
||||||
|
tests/test_key_files.sh \
|
||||||
tests/test_legacy_disk_5_2_1.sh \
|
tests/test_legacy_disk_5_2_1.sh \
|
||||||
tests/test_path_traversal.sh \
|
tests/test_path_traversal.sh \
|
||||||
tests/test_pqbox.sh \
|
tests/test_pqbox.sh \
|
||||||
|
|
@ -121,6 +122,8 @@ jobs:
|
||||||
run: make CC=${{ matrix.cc }} V=1 WITH_SDK=0 WITH_PQBOX=0 check
|
run: make CC=${{ matrix.cc }} V=1 WITH_SDK=0 WITH_PQBOX=0 check
|
||||||
- name: Extended upstream tests
|
- name: Extended upstream tests
|
||||||
run: make CC=${{ matrix.cc }} V=1 WITH_SDK=0 WITH_PQBOX=0 test-all
|
run: make CC=${{ matrix.cc }} V=1 WITH_SDK=0 WITH_PQBOX=0 test-all
|
||||||
|
- name: In-tree SDK atomic key-save regression
|
||||||
|
run: make CC=${{ matrix.cc }} V=1 sdk-test
|
||||||
- name: Functional test of the built CLI
|
- name: Functional test of the built CLI
|
||||||
run: bash scripts/test-installed-zupt.sh "$PWD/zupt"
|
run: bash scripts/test-installed-zupt.sh "$PWD/zupt"
|
||||||
|
|
||||||
|
|
@ -377,11 +380,13 @@ jobs:
|
||||||
# Use osc's installed service executor to validate this standalone,
|
# Use osc's installed service executor to validate this standalone,
|
||||||
# repository-owned _service file with the exact same local services.
|
# repository-owned _service file with the exact same local services.
|
||||||
python3 - "$service_dir" <<'PY'
|
python3 - "$service_dir" <<'PY'
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
from xml.etree import ElementTree
|
from xml.etree import ElementTree
|
||||||
from osc.obs_scm.serviceinfo import Serviceinfo
|
from osc.obs_scm.serviceinfo import Serviceinfo
|
||||||
|
|
||||||
service_dir = sys.argv[1]
|
service_dir = sys.argv[1]
|
||||||
|
os.chdir(service_dir)
|
||||||
service_info = Serviceinfo()
|
service_info = Serviceinfo()
|
||||||
service_info.read(ElementTree.parse(f"{service_dir}/_service").getroot())
|
service_info.read(ElementTree.parse(f"{service_dir}/_service").getroot())
|
||||||
raise SystemExit(service_info.execute(service_dir, "all", verbose=True))
|
raise SystemExit(service_info.execute(service_dir, "all", verbose=True))
|
||||||
|
|
@ -464,6 +469,12 @@ jobs:
|
||||||
test "$(rpm -qp --qf '%{ARCH}' "$gui_rpm")" = noarch
|
test "$(rpm -qp --qf '%{ARCH}' "$gui_rpm")" = noarch
|
||||||
rpm -qp --requires "$gui_rpm" | grep -Fx "zupt >= $version"
|
rpm -qp --requires "$gui_rpm" | grep -Fx "zupt >= $version"
|
||||||
test "$(rpm -qp --qf '%{NAME}' "$gui_srpm")" = zupt-gui
|
test "$(rpm -qp --qf '%{NAME}' "$gui_srpm")" = zupt-gui
|
||||||
|
test "$(rpm -qp --qf '%{VERSION}-%{RELEASE}' "$gui_srpm")" = "$version-1"
|
||||||
|
test "$(rpm -qp --qf '%{SOURCEPACKAGE}' "$gui_srpm")" = 1
|
||||||
|
test "$(rpm -qp --qf '%{SOURCERPM}' "$gui_srpm")" = '(none)'
|
||||||
|
test "$(rpm -qpl "$gui_srpm" | wc -l)" -eq 2
|
||||||
|
rpm -qpl "$gui_srpm" | grep -Fx "zupt-gui-$version.tar.gz"
|
||||||
|
rpm -qpl "$gui_srpm" | grep -Fx zupt-gui.spec
|
||||||
- name: Build the matching Fedora CLI RPM
|
- name: Build the matching Fedora CLI RPM
|
||||||
run: |
|
run: |
|
||||||
version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
|
version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
|
||||||
|
|
|
||||||
21
.github/workflows/cross-platform.yml
vendored
21
.github/workflows/cross-platform.yml
vendored
|
|
@ -86,20 +86,33 @@ jobs:
|
||||||
trap 'chmod -R u+rwX "$test_root" 2>/dev/null || true; rm -rf -- "$test_root"' EXIT
|
trap 'chmod -R u+rwX "$test_root" 2>/dev/null || true; rm -rf -- "$test_root"' EXIT
|
||||||
output_dir="$test_root/saída-安全"
|
output_dir="$test_root/saída-安全"
|
||||||
archive="$test_root/cópia-安全.zupt"
|
archive="$test_root/cópia-安全.zupt"
|
||||||
|
emoji_name=$'emoji-\xF0\x9F\x98\x80.bin'
|
||||||
mkdir -p "$test_root/input/subdir" "$output_dir"
|
mkdir -p "$test_root/input/subdir" "$output_dir"
|
||||||
printf 'Windows release smoke test\n' > "$test_root/input/café.txt"
|
printf 'Windows release smoke test\n' > "$test_root/input/café.txt"
|
||||||
printf 'UTF-8: café-安全\n' > "$test_root/input/subdir/ação-安全.txt"
|
printf 'UTF-8: café-安全\n' > "$test_root/input/subdir/ação-安全.txt"
|
||||||
printf 'emoji filename\n' > "$test_root/input/subdir/emoji-😀.bin"
|
printf 'emoji filename\n' > "$test_root/input/subdir/$emoji_name"
|
||||||
: > "$test_root/input/empty"
|
: > "$test_root/input/empty"
|
||||||
dd if=/dev/urandom of="$test_root/input/subdir/random.bin" bs=4096 count=4 2>/dev/null
|
dd if=/dev/urandom of="$test_root/input/subdir/random.bin" bs=4096 count=4 2>/dev/null
|
||||||
(cd "$test_root" && "$exe" compress "$archive" input)
|
(cd "$test_root" && "$exe" compress "$archive" input)
|
||||||
"$exe" test "$archive"
|
"$exe" test "$archive"
|
||||||
"$exe" list "$archive" > "$test_root/list.txt" 2>&1
|
"$exe" list "$archive" > "$test_root/list.txt" 2>&1
|
||||||
grep -F 'café.txt' "$test_root/list.txt"
|
|
||||||
grep -F 'ação-安全.txt' "$test_root/list.txt"
|
|
||||||
grep -F 'emoji-😀.bin' "$test_root/list.txt"
|
|
||||||
"$exe" extract -o "$output_dir" "$archive"
|
"$exe" extract -o "$output_dir" "$archive"
|
||||||
diff -r "$test_root/input" "$output_dir/input"
|
diff -r "$test_root/input" "$output_dir/input"
|
||||||
|
python3 - "$test_root/list.txt" <<'PY'
|
||||||
|
import pathlib
|
||||||
|
import sys
|
||||||
|
|
||||||
|
listing = pathlib.Path(sys.argv[1]).read_bytes()
|
||||||
|
expected = {
|
||||||
|
"Latin-1": bytes.fromhex("636166c3a92e747874"),
|
||||||
|
"BMP": bytes.fromhex("61c3a7c3a36f2de5ae89e585a82e747874"),
|
||||||
|
"non-BMP": bytes.fromhex("656d6f6a692df09f98802e62696e"),
|
||||||
|
}
|
||||||
|
missing = [label for label, name in expected.items() if name not in listing]
|
||||||
|
if missing:
|
||||||
|
raise SystemExit("list output is missing exact UTF-8 names: " +
|
||||||
|
", ".join(missing))
|
||||||
|
PY
|
||||||
objdump -p "$exe" > "$test_root/imports.txt"
|
objdump -p "$exe" > "$test_root/imports.txt"
|
||||||
if grep -Eqi '(vendor[/\\]|libvuptsdk|libpqvaptvupt|libgcc_s|libstdc\+\+|libwinpthread|msys-2[.]0|cygwin1)[^[:space:]]*[.]dll' \
|
if grep -Eqi '(vendor[/\\]|libvuptsdk|libpqvaptvupt|libgcc_s|libstdc\+\+|libwinpthread|msys-2[.]0|cygwin1)[^[:space:]]*[.]dll' \
|
||||||
"$test_root/imports.txt"; then
|
"$test_root/imports.txt"; then
|
||||||
|
|
|
||||||
18
.github/workflows/promote-release.yml
vendored
18
.github/workflows/promote-release.yml
vendored
|
|
@ -11,7 +11,7 @@ on:
|
||||||
required: true
|
required: true
|
||||||
type: number
|
type: number
|
||||||
tag:
|
tag:
|
||||||
description: Existing annotated release tag, for example v5.2.3
|
description: Existing annotated release tag, for example v5.2.8
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
|
||||||
|
|
@ -389,11 +389,17 @@ jobs:
|
||||||
[[ $(rpm -qp --qf '%{VERSION}' "$rpm_file") == "$VERSION" ]]
|
[[ $(rpm -qp --qf '%{VERSION}' "$rpm_file") == "$VERSION" ]]
|
||||||
[[ $(rpm -qp --qf '%{RELEASE}' "$rpm_file") == 0 ]]
|
[[ $(rpm -qp --qf '%{RELEASE}' "$rpm_file") == 0 ]]
|
||||||
[[ $(rpm -qp --qf '%{ARCH}' "$rpm_file") == x86_64 ]]
|
[[ $(rpm -qp --qf '%{ARCH}' "$rpm_file") == x86_64 ]]
|
||||||
|
[[ $(rpm -qp --qf '%{SOURCEPACKAGE}' "$rpm_file") == '(none)' ]]
|
||||||
|
[[ $(rpm -qp --qf '%{SOURCERPM}' "$rpm_file") == "$srpm_name" ]]
|
||||||
srpm=$asset_dir/$srpm_name
|
srpm=$asset_dir/$srpm_name
|
||||||
[[ $(rpm -qp --qf '%{NAME}' "$srpm") == zupt ]]
|
[[ $(rpm -qp --qf '%{NAME}' "$srpm") == zupt ]]
|
||||||
[[ $(rpm -qp --qf '%{VERSION}' "$srpm") == "$VERSION" ]]
|
[[ $(rpm -qp --qf '%{VERSION}' "$srpm") == "$VERSION" ]]
|
||||||
[[ $(rpm -qp --qf '%{RELEASE}' "$srpm") == 0 ]]
|
[[ $(rpm -qp --qf '%{RELEASE}' "$srpm") == 0 ]]
|
||||||
[[ $(rpm -qp --qf '%{ARCH}' "$srpm") == src ]]
|
[[ $(rpm -qp --qf '%{SOURCEPACKAGE}' "$srpm") == 1 ]]
|
||||||
|
[[ $(rpm -qp --qf '%{SOURCERPM}' "$srpm") == '(none)' ]]
|
||||||
|
[[ $(rpm -qpl "$srpm" | wc -l) -eq 2 ]]
|
||||||
|
rpm -qpl "$srpm" | grep -Fx "zupt-$VERSION.tar.gz"
|
||||||
|
rpm -qpl "$srpm" | grep -Fx zupt.spec
|
||||||
|
|
||||||
gui_deb=$asset_dir/$gui_deb_name
|
gui_deb=$asset_dir/$gui_deb_name
|
||||||
dpkg-deb --info "$gui_deb" >/dev/null
|
dpkg-deb --info "$gui_deb" >/dev/null
|
||||||
|
|
@ -406,12 +412,18 @@ jobs:
|
||||||
[[ $(rpm -qp --qf '%{VERSION}' "$gui_rpm") == "$VERSION" ]]
|
[[ $(rpm -qp --qf '%{VERSION}' "$gui_rpm") == "$VERSION" ]]
|
||||||
[[ $(rpm -qp --qf '%{RELEASE}' "$gui_rpm") == 1 ]]
|
[[ $(rpm -qp --qf '%{RELEASE}' "$gui_rpm") == 1 ]]
|
||||||
[[ $(rpm -qp --qf '%{ARCH}' "$gui_rpm") == noarch ]]
|
[[ $(rpm -qp --qf '%{ARCH}' "$gui_rpm") == noarch ]]
|
||||||
|
[[ $(rpm -qp --qf '%{SOURCEPACKAGE}' "$gui_rpm") == '(none)' ]]
|
||||||
|
[[ $(rpm -qp --qf '%{SOURCERPM}' "$gui_rpm") == "$gui_srpm_name" ]]
|
||||||
rpm -qp --requires "$gui_rpm" | grep -Fx "zupt >= $VERSION"
|
rpm -qp --requires "$gui_rpm" | grep -Fx "zupt >= $VERSION"
|
||||||
gui_srpm=$asset_dir/$gui_srpm_name
|
gui_srpm=$asset_dir/$gui_srpm_name
|
||||||
[[ $(rpm -qp --qf '%{NAME}' "$gui_srpm") == zupt-gui ]]
|
[[ $(rpm -qp --qf '%{NAME}' "$gui_srpm") == zupt-gui ]]
|
||||||
[[ $(rpm -qp --qf '%{VERSION}' "$gui_srpm") == "$VERSION" ]]
|
[[ $(rpm -qp --qf '%{VERSION}' "$gui_srpm") == "$VERSION" ]]
|
||||||
[[ $(rpm -qp --qf '%{RELEASE}' "$gui_srpm") == 1 ]]
|
[[ $(rpm -qp --qf '%{RELEASE}' "$gui_srpm") == 1 ]]
|
||||||
[[ $(rpm -qp --qf '%{ARCH}' "$gui_srpm") == src ]]
|
[[ $(rpm -qp --qf '%{SOURCEPACKAGE}' "$gui_srpm") == 1 ]]
|
||||||
|
[[ $(rpm -qp --qf '%{SOURCERPM}' "$gui_srpm") == '(none)' ]]
|
||||||
|
[[ $(rpm -qpl "$gui_srpm" | wc -l) -eq 2 ]]
|
||||||
|
rpm -qpl "$gui_srpm" | grep -Fx "zupt-gui-$VERSION.tar.gz"
|
||||||
|
rpm -qpl "$gui_srpm" | grep -Fx zupt-gui.spec
|
||||||
|
|
||||||
linux_tar=$asset_dir/$linux_tar_name
|
linux_tar=$asset_dir/$linux_tar_name
|
||||||
python3 - "$linux_tar" "zupt-$VERSION-linux-x86_64" <<'PY'
|
python3 - "$linux_tar" "zupt-$VERSION-linux-x86_64" <<'PY'
|
||||||
|
|
|
||||||
170
AUDIT.md
170
AUDIT.md
|
|
@ -1,12 +1,12 @@
|
||||||
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
||||||
# ZUPT 5.2.3 audit guide and finding history
|
# ZUPT 5.2.8 audit guide and finding history
|
||||||
|
|
||||||
This document describes review surfaces and reproducible checks. It is an
|
This document describes review surfaces and reproducible checks. It is an
|
||||||
upstream self-review, not an independent audit, certification, or guarantee.
|
upstream self-review, not an independent audit, certification, or guarantee.
|
||||||
`SECURITY.md` defines reporting policy and `THREAT_MODEL.md` defines the
|
`SECURITY.md` defines reporting policy and `THREAT_MODEL.md` defines the
|
||||||
security boundary.
|
security boundary.
|
||||||
|
|
||||||
## 5.2.3 scope
|
## 5.2.8 scope
|
||||||
|
|
||||||
The baseline scope is the source-only CLI and its bundled source codec:
|
The baseline scope is the source-only CLI and its bundled source codec:
|
||||||
|
|
||||||
|
|
@ -28,7 +28,7 @@ output.
|
||||||
|
|
||||||
## Source-only review
|
## Source-only review
|
||||||
|
|
||||||
The 5.2.3 baseline retains the source-only boundary introduced in 5.2.2, which
|
The 5.2.8 baseline retains the source-only boundary introduced in 5.2.2, which
|
||||||
removed incomplete SDK/PQBOX header snapshots and local precompiled-library
|
removed incomplete SDK/PQBOX header snapshots and local precompiled-library
|
||||||
expectations. Git and new upstream source
|
expectations. Git and new upstream source
|
||||||
archives are intended to contain no compiled executable, object, shared/static
|
archives are intended to contain no compiled executable, object, shared/static
|
||||||
|
|
@ -42,10 +42,10 @@ scripts/check-source-only.sh
|
||||||
|
|
||||||
# committed Git tree or immutable tag
|
# committed Git tree or immutable tag
|
||||||
scripts/check-source-only.sh --tag HEAD
|
scripts/check-source-only.sh --tag HEAD
|
||||||
scripts/check-source-only.sh --tag v5.2.3
|
scripts/check-source-only.sh --tag v5.2.8
|
||||||
|
|
||||||
# generated source archive
|
# generated source archive
|
||||||
scripts/check-source-only.sh --archive /path/to/zupt-5.2.3.tar.gz
|
scripts/check-source-only.sh --archive /path/to/zupt-5.2.8.tar.gz
|
||||||
```
|
```
|
||||||
|
|
||||||
The scanner checks extensions and magic bytes, nested archives, symlink targets,
|
The scanner checks extensions and magic bytes, nested archives, symlink targets,
|
||||||
|
|
@ -80,6 +80,7 @@ make -j"$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf 1)" \
|
||||||
WITH_SDK=0 WITH_PQBOX=0 V=1
|
WITH_SDK=0 WITH_PQBOX=0 V=1
|
||||||
make WITH_SDK=0 WITH_PQBOX=0 check
|
make WITH_SDK=0 WITH_PQBOX=0 check
|
||||||
make WITH_SDK=0 WITH_PQBOX=0 test-all
|
make WITH_SDK=0 WITH_PQBOX=0 test-all
|
||||||
|
make sdk-test
|
||||||
```
|
```
|
||||||
|
|
||||||
Relevant review layers include:
|
Relevant review layers include:
|
||||||
|
|
@ -92,6 +93,7 @@ Relevant review layers include:
|
||||||
| Archive behavior | quick/regression, traversal, argument-order, block-swap, nonce, and exact-size tests | Exercises current parser, integrity, and round-trip properties |
|
| Archive behavior | quick/regression, traversal, argument-order, block-swap, nonce, and exact-size tests | Exercises current parser, integrity, and round-trip properties |
|
||||||
| Password sources | `tests/test_password_sources.sh` | Exercises password-file, inherited-descriptor and explicit-prompt rejection paths without logging password contents |
|
| Password sources | `tests/test_password_sources.sh` | Exercises password-file, inherited-descriptor and explicit-prompt rejection paths without logging password contents |
|
||||||
| Key files | native key regressions | Exercises no-replace private-file creation, POSIX mode `0600`/Windows current-user-only DACL, failed-partial behavior, checksum, and exact ZKEY/ZPQK version/flags/reserved/size/role validation |
|
| Key files | native key regressions | Exercises no-replace private-file creation, POSIX mode `0600`/Windows current-user-only DACL, failed-partial behavior, checksum, and exact ZKEY/ZPQK version/flags/reserved/size/role validation |
|
||||||
|
| SDK key publication | `make sdk-test` | Exercises atomic descriptor/handle-backed key copies, POSIX private/public modes, and symlink/hardlink target preservation; this now runs in `release-check` and hosted GCC/Clang Linux CI |
|
||||||
| Terminal output | archive-comment regression | Requires displayed untrusted comments to contain no raw terminal-control sequence |
|
| Terminal output | archive-comment regression | Requires displayed untrusted comments to contain no raw terminal-control sequence |
|
||||||
| Prompt cleanup | PTY signal regression | Requires handled POSIX interruption to restore the saved terminal state |
|
| Prompt cleanup | PTY signal regression | Requires handled POSIX interruption to restore the saved terminal state |
|
||||||
| Sanitizers | `make test-asan-run` | Builds and executes separate ASan/UBSan/LSan evidence where supported; not a substitute for normal tests |
|
| Sanitizers | `make test-asan-run` | Builds and executes separate ASan/UBSan/LSan evidence where supported; not a substitute for normal tests |
|
||||||
|
|
@ -109,7 +111,7 @@ without evidence.
|
||||||
The following upstream self-audit results apply only to the 5.2.2 candidate at
|
The following upstream self-audit results apply only to the 5.2.2 candidate at
|
||||||
commit `ff99770` on the recorded local Linux environments. The immutable 5.2.2
|
commit `ff99770` on the recorded local Linux environments. The immutable 5.2.2
|
||||||
tag was not promoted after post-tag CI integration failures. These results are
|
tag was not promoted after post-tag CI integration failures. These results are
|
||||||
not independent certification, a 5.2.3 result, or evidence that release assets
|
not independent certification, a 5.2.8 result, or evidence that release assets
|
||||||
were published.
|
were published.
|
||||||
|
|
||||||
| Gate | Result | Recorded evidence |
|
| Gate | Result | Recorded evidence |
|
||||||
|
|
@ -123,11 +125,147 @@ were published.
|
||||||
| Mutation fuzzing | PASS | 1,000 mutation iterations completed without a sanitizer-detected crash. |
|
| Mutation fuzzing | PASS | 1,000 mutation iterations completed without a sanitizer-detected crash. |
|
||||||
|
|
||||||
An earlier off-screen GUI smoke run remains supporting evidence, but is not
|
An earlier off-screen GUI smoke run remains supporting evidence, but is not
|
||||||
represented as an exact-`ff99770` GUI-package result. The exact 5.2.3 candidate
|
represented as an exact-`ff99770` GUI-package result. The immutable 5.2.3
|
||||||
must repeat the required suite. Native Windows and macOS gates, hosted GitHub CI
|
candidate was not promoted because its source-policy test assumed LF for a
|
||||||
and release promotion, authenticated OBS validation, and resolution of the
|
Windows `.bat` checkout that correctly used CRLF.
|
||||||
openSUSE automatic `debugsource` rpmlint `no-binary` finding remain pending
|
|
||||||
until recorded otherwise.
|
## Prior 5.2.4 exact-tag integration evidence
|
||||||
|
|
||||||
|
GitHub Actions exact-tag run `33431386002` completed 12 jobs successfully. Its
|
||||||
|
sole failed job was the openSUSE gate: the standalone `Serviceinfo` harness did
|
||||||
|
not change into the directory containing `_service` before executing the
|
||||||
|
service chain. Dependent native Windows and macOS jobs were therefore skipped,
|
||||||
|
and v5.2.4 was not promoted. The tag and its record remain immutable.
|
||||||
|
|
||||||
|
A separate local openSUSE Tumbleweed reproduction resolved the explicit
|
||||||
|
`refs/tags/v5.2.4` revision to the tagged commit and, after
|
||||||
|
`os.chdir(service_dir)`, completed `obs_scm`, `tar`, and `recompress`. It
|
||||||
|
produced exactly one `zupt-5.2.4.tar.gz`, which passed the source-only scanner.
|
||||||
|
This isolates a release/test harness defect; it is not evidence of a product,
|
||||||
|
archive-format, cryptographic, codec, or SDK ABI change. It also does not turn
|
||||||
|
the skipped native jobs into passes or transfer any result to 5.2.8.
|
||||||
|
|
||||||
|
## Prior 5.2.5 exact-tag native-gate evidence
|
||||||
|
|
||||||
|
The immutable `v5.2.5` candidate was not promoted. Exact-tag GitHub Actions run
|
||||||
|
`33434986357` completed 13 jobs successfully, while its native Windows and
|
||||||
|
macOS jobs failed. The Windows regression did not preserve every requested
|
||||||
|
hostile path byte across its command-line boundary. The macOS gate exposed both
|
||||||
|
an unavailable `explicit_bzero` assumption and Bash 3.2 empty-array behavior in
|
||||||
|
the source scanner exercised by `make check`.
|
||||||
|
|
||||||
|
The 5.2.6 corrections select the existing compiler-resistant volatile wipe on
|
||||||
|
Darwin and NetBSD, guard every relevant scanner array, and make the Windows
|
||||||
|
fixture accept explicit hexadecimal bytes, verify the full requested path in
|
||||||
|
the archive, and reject each dangerous raw byte fragment anywhere in diagnostic
|
||||||
|
output. These changes do not alter the archive format, cryptography, bundled
|
||||||
|
codec, or SDK ABI.
|
||||||
|
|
||||||
|
A separate local compatibility run executed the corrected scanner with genuine
|
||||||
|
GNU Bash 3.2.57 in a clean clone. All four exercised modes completed: the
|
||||||
|
repository audit reported 609 files and one archive; `--tree` reported 204/0;
|
||||||
|
`--archive` reported 201/1; and `--root` plus `--tag v5.2.5` reported 810/2.
|
||||||
|
This is targeted scanner compatibility evidence only, not exact-v5.2.6 or
|
||||||
|
v5.2.8 hosted CI, package, native-platform, or promotion evidence.
|
||||||
|
|
||||||
|
## Prior 5.2.6 exact-tag native-gate evidence
|
||||||
|
|
||||||
|
The immutable `v5.2.6` candidate was not promoted. Exact-tag GitHub Actions run
|
||||||
|
`33442264243` completed 13 jobs successfully and failed two native jobs. On
|
||||||
|
macOS arm64, the strict SHA-NI regression build diagnosed x86-only helper
|
||||||
|
declarations as unused under `-Werror`. On Windows, argv transcoding of the safe
|
||||||
|
printable UTF-8 fixture caused the path regression to abort before its intended
|
||||||
|
archive and diagnostic assertions.
|
||||||
|
|
||||||
|
The 5.2.7 changes scope those helper declarations to supported x86 builds and
|
||||||
|
carry the safe UTF-8 fixture across the Windows argument boundary without
|
||||||
|
locale-dependent byte conversion. These are test/release integration changes,
|
||||||
|
not archive-format, cryptographic, codec, or SDK ABI changes.
|
||||||
|
|
||||||
|
## Prior 5.2.7 exact-tag native-gate evidence
|
||||||
|
|
||||||
|
The immutable `v5.2.7` candidate was not promoted. Exact-tag GitHub Actions run
|
||||||
|
`33445470664` concluded `cancelled` at `2026-08-31T23:11:19Z`, with 13
|
||||||
|
successful jobs, one failed macOS job, and one cancelled Windows job. The macOS
|
||||||
|
runner filesystem rejected creation of the
|
||||||
|
raw-C1 filename fixture with `EILSEQ`. The hosted Windows job stalled in `make
|
||||||
|
check`; a MinGW/Wine reproduction isolated the cause to
|
||||||
|
`test --password-prompt ... </dev/null` entering `_getch`, and the remaining
|
||||||
|
native job was cancelled.
|
||||||
|
|
||||||
|
The 5.2.8 fixture treats that creation refusal as an explicit `SKIP`; on a
|
||||||
|
filesystem that accepts the byte, the scanner must still reject the compiled
|
||||||
|
magic and render the path without emitting the raw C1 byte. No result from the
|
||||||
|
immutable tag transfers automatically to 5.2.8. The Windows prompt correction
|
||||||
|
rejects a redirected/non-console standard input before entering `_getch` and
|
||||||
|
adds a native EOF regression; it also requires fresh exact-candidate evidence.
|
||||||
|
|
||||||
|
## 5.2.8 CodeQL High path-race corrections
|
||||||
|
|
||||||
|
CodeQL High #5 identified SDK key-copy publication that reopened the output
|
||||||
|
path and then applied `chmod` to that mutable name. The copy now uses the core
|
||||||
|
atomic publisher, sets POSIX mode on the open temporary descriptor, checks
|
||||||
|
read/close/publication failures, and replaces only the destination directory
|
||||||
|
entry. `sdk-test` covers symlink and hardlink sentinels and expected key modes.
|
||||||
|
|
||||||
|
CodeQL High #6 identified the POSIX disk-restore `lstat`-then-`open` sequence.
|
||||||
|
Restore now opens once with `O_NOFOLLOW` and without truncation, classifies that
|
||||||
|
descriptor with `fstat`, and retains the same raw-device descriptor through
|
||||||
|
capacity checks and writes. Regular-file destinations retain the existing
|
||||||
|
atomic publisher.
|
||||||
|
|
||||||
|
CodeQL High #7 identified benchmark cleanup's `lstat`-then-recursive-path
|
||||||
|
sequence. POSIX cleanup now pins each component with `openat` and removes leaves
|
||||||
|
with `unlinkat`; Windows pins ancestors/children, opens reparse points without
|
||||||
|
following them, recurses only into a plain directory held against rename, and
|
||||||
|
after emptying a directory reopens it relative to the pinned parent, verifies
|
||||||
|
its volume and file index against the traversal handle, and marks only that
|
||||||
|
identity-checked handle for deletion. The live-workspace
|
||||||
|
regression injects a directory symlink and verifies that its external sentinel
|
||||||
|
survives. These are reviewed fixes and regression coverage, not independent
|
||||||
|
certification by themselves; the exact-tag evidence is recorded below.
|
||||||
|
|
||||||
|
The C/C++ default-branch analysis of commit `69fc26b` closed #5, #6, and #7,
|
||||||
|
then reported High #8, #9, and #10 solely in the newly added SDK regression:
|
||||||
|
its sentinel and mode checks used `stat`/`lstat` before later path operations.
|
||||||
|
Each individual content or metadata check now opens without following links
|
||||||
|
and uses `fstat` or reads through that already-open descriptor. A static guard
|
||||||
|
rejects a return to path-level `stat`/`lstat` in this test. The subsequent
|
||||||
|
C/C++ default-branch scan run `33452563116` completed successfully at commit
|
||||||
|
`7a8e5c5`; alerts #5 through #10 are fixed, and the authenticated
|
||||||
|
code-scanning API reported zero open alerts.
|
||||||
|
|
||||||
|
Manual pre-tag CI run `33452602634` at the same functional commit completed 14
|
||||||
|
of 15 jobs successfully. Linux compilers, analyzers, sanitizers, source policy,
|
||||||
|
reproducibility, DEB, RPM/SRPM, portable bundles, and the native macOS DMG gate
|
||||||
|
passed. On Windows, source audit, build, and `make check` passed; the subsequent
|
||||||
|
smoke failed only when the old MSYS `grep` tried to match a literal non-BMP
|
||||||
|
filename after ZUPT had compressed and verified all five inputs. Independent
|
||||||
|
MinGW/Wine reproduction confirmed that the redirected ZUPT listing contained
|
||||||
|
the exact UTF-8 bytes. The gate now creates the non-BMP name through byte
|
||||||
|
escapes, verifies Latin-1/BMP/non-BMP listing bytes without locale-sensitive
|
||||||
|
matching, and requires extraction plus a full tree diff. The path-confinement
|
||||||
|
regression independently constructs the BMP/non-BMP archive name from ASCII
|
||||||
|
hex and requires byte-exact listing and extraction.
|
||||||
|
|
||||||
|
Because that pre-tag run failed, it remains diagnostic evidence rather than
|
||||||
|
release approval. The immutable `v5.2.8` tag at
|
||||||
|
`ebb9ab3aa1d42c50030ca02883f6162dc4771fe1` repeated the complete suite in
|
||||||
|
manually dispatched run `33456209269`: all 15 jobs passed, including native
|
||||||
|
Windows/macOS, the pinned local OBS service chain, source reproducibility,
|
||||||
|
DEB/RPM/SRPM, installed-package, sanitizer, analyzer, and source-only gates.
|
||||||
|
The canonical source archive was reproduced at 798296 bytes with SHA-256
|
||||||
|
`378b9506211545b9594cf0d38ac8955d9b1cac34eb6b379ae0ec26b84edb65f7`.
|
||||||
|
|
||||||
|
Initial promotion run `33457344882` stopped before release creation because
|
||||||
|
its validator incorrectly assumed that an SRPM's `%{ARCH}` must be `src`.
|
||||||
|
Both artifacts were genuine source packages: `%{SOURCEPACKAGE}` was `1`,
|
||||||
|
`%{SOURCERPM}` was absent, each payload was exactly its Source0 plus spec, and
|
||||||
|
each binary RPM referenced the matching SRPM. Commit `33eb904` changed the
|
||||||
|
gate to those canonical metadata and payload checks. Corrected promotion run
|
||||||
|
`33457868306` then validated the same tag-bound artifacts and published exactly
|
||||||
|
13 assets. No asset was rebuilt to pass promotion. These are reproducible
|
||||||
|
project records, not independent certification.
|
||||||
|
|
||||||
## Cryptographic review boundary
|
## Cryptographic review boundary
|
||||||
|
|
||||||
|
|
@ -146,7 +284,7 @@ AES implementation has documented cache-timing risk on hostile shared hardware.
|
||||||
|
|
||||||
The following entries are retained as release history. Their regression tests
|
The following entries are retained as release history. Their regression tests
|
||||||
should be rerun, but the historical resolution does not itself constitute a
|
should be rerun, but the historical resolution does not itself constitute a
|
||||||
5.2.3 test result.
|
5.2.8 test result.
|
||||||
|
|
||||||
| First corrected | Severity | Finding | Resolution recorded at the time |
|
| First corrected | Severity | Finding | Resolution recorded at the time |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
|
|
@ -166,6 +304,10 @@ should be rerun, but the historical resolution does not itself constitute a
|
||||||
| 5.2.2 | Medium | A signal during an interactive POSIX password prompt could leave terminal echo/state altered | Restore saved terminal settings on handled interruptions; cover the behavior with a PTY regression |
|
| 5.2.2 | Medium | A signal during an interactive POSIX password prompt could leave terminal echo/state altered | Restore saved terminal settings on handled interruptions; cover the behavior with a PTY regression |
|
||||||
| 5.2.2 | Test reliability | `tests/regression.sh` used Bash syntax without making the interpreter contract explicit | Execute the suite explicitly with Bash and keep syntax/interpreter checks in release gates |
|
| 5.2.2 | Test reliability | `tests/regression.sh` used Bash syntax without making the interpreter contract explicit | Execute the suite explicitly with Bash and keep syntax/interpreter checks in release gates |
|
||||||
| 5.2.2 | Documentation/licensing | Current documentation incorrectly denied historical MIT grants visible in published Git history | Added a factual erratum: current source follows current SPDX notices, while earlier grants and immutable tags remain valid and unmodified |
|
| 5.2.2 | Documentation/licensing | Current documentation incorrectly denied historical MIT grants visible in published Git history | Added a factual erratum: current source follows current SPDX notices, while earlier grants and immutable tags remain valid and unmodified |
|
||||||
|
| 5.2.8 | High | CodeQL #5: SDK key copies changed permissions through a re-resolved destination path | Publish through the core atomic output object and apply permissions to its open descriptor; run link-target/mode regressions through `sdk-test` |
|
||||||
|
| 5.2.8 | High | CodeQL #6: POSIX disk restore classified a pathname before reopening it destructively | Open without truncation or symlink following, classify with `fstat`, and retain the same device descriptor through write |
|
||||||
|
| 5.2.8 | High | CodeQL #7: benchmark cleanup classified entries before recursively resolving their path | Traverse pinned descriptors/handles, refuse link/reparse traversal, remove entries relative to pinned parents, and verify Windows directory identity before handle deletion |
|
||||||
|
| 5.2.8 | High (test-only) | CodeQL #8/#9/#10: the new SDK regression inspected paths before later reads or cleanup | Open each fixture without following links, inspect/read through `fstat` and the same descriptor, and reject path-level `stat`/`lstat` in the static gate |
|
||||||
|
|
||||||
See `CHANGELOG.md` for the complete per-release history and compatibility notes.
|
See `CHANGELOG.md` for the complete per-release history and compatibility notes.
|
||||||
Old tags remain immutable and may contain artifacts or build assumptions removed
|
Old tags remain immutable and may contain artifacts or build assumptions removed
|
||||||
|
|
@ -187,12 +329,12 @@ include SHA-256 checksums. The gated GUI set adds the architecture-independent
|
||||||
DEB, noarch/source RPM, and source-only portable GUI ZIP. Package gates include
|
DEB, noarch/source RPM, and source-only portable GUI ZIP. Package gates include
|
||||||
exact payload/dependency and installed off-screen integration checks; the
|
exact payload/dependency and installed off-screen integration checks; the
|
||||||
portable ZIP additionally receives source scans, an exact safe-member allowlist,
|
portable ZIP additionally receives source scans, an exact safe-member allowlist,
|
||||||
and an extracted launcher test. An AppImage is not promoted by the 5.2.3
|
and an extracted launcher test. An AppImage is not promoted by the 5.2.8
|
||||||
policy; AppDir and Flatpak bundles, GUI platform installers, and bare
|
policy; AppDir and Flatpak bundles, GUI platform installers, and bare
|
||||||
Linux/Windows executables are also excluded. Windows ZIP and macOS DMG outputs
|
Linux/Windows executables are also excluded. Windows ZIP and macOS DMG outputs
|
||||||
remain CLI-only.
|
remain CLI-only.
|
||||||
|
|
||||||
No Wine result is retained as release evidence for 5.2.3. Cross-compilation
|
No Wine result is retained as release evidence for 5.2.8. Cross-compilation
|
||||||
does not establish native-Windows behavior. Extended-length/device namespace
|
does not establish native-Windows behavior. Extended-length/device namespace
|
||||||
paths, raw UNC output roots, and mapped/network-drive output are unsupported;
|
paths, raw UNC output roots, and mapped/network-drive output are unsupported;
|
||||||
the native Windows workflow remains a publication gate for the ZIP containing
|
the native Windows workflow remains a publication gate for the ZIP containing
|
||||||
|
|
|
||||||
194
CHANGELOG.md
194
CHANGELOG.md
|
|
@ -1,5 +1,162 @@
|
||||||
# ZUPT Changelog
|
# ZUPT Changelog
|
||||||
|
|
||||||
|
## [5.2.8] — 2026-08-31 — Path-race hardening and native-fixture correction
|
||||||
|
|
||||||
|
Corrective successor to the immutable, unpromoted `v5.2.7` candidate.
|
||||||
|
Exact-tag GitHub Actions run `33445470664` reached a macOS failure because the
|
||||||
|
runner filesystem rejected creation of the raw-C1 filename fixture with
|
||||||
|
`EILSEQ`. The workflow concluded `cancelled` at `2026-08-31T23:11:19Z`, with
|
||||||
|
13 successful jobs, one failed macOS job, and one cancelled Windows job.
|
||||||
|
The hosted Windows job stalled in `make check`; a MinGW/Wine reproduction
|
||||||
|
isolated the cause to `test --password-prompt ... </dev/null` entering
|
||||||
|
`_getch` despite redirected standard input. The tag and its evidence remain
|
||||||
|
unchanged.
|
||||||
|
|
||||||
|
- Close CodeQL High #5 in SDK key saving by copying into the core atomic
|
||||||
|
publisher's already-open private object, applying POSIX key permissions with
|
||||||
|
`fchmod` on that descriptor, checking read/close/publication failures, and
|
||||||
|
replacing only the requested directory entry. Symlink and hardlink target
|
||||||
|
sentinels and private/public key modes are covered by the SDK regression.
|
||||||
|
- Close CodeQL High #6 in POSIX disk restore by opening the target once without
|
||||||
|
truncation or final-symlink following, classifying that descriptor with
|
||||||
|
`fstat`, and retaining the same device descriptor through capacity checks and
|
||||||
|
writes. Regular-file restores continue to use atomic publication.
|
||||||
|
- Close CodeQL High #7 in benchmark cleanup by resolving POSIX components with
|
||||||
|
`openat(..., O_NOFOLLOW)`, deleting relative to pinned descriptors with
|
||||||
|
`unlinkat`, and using pinned, reparse-point-aware handles for Windows
|
||||||
|
traversal. The regression injects a directory symlink into a live workspace
|
||||||
|
and verifies that cleanup does not visit its target.
|
||||||
|
- The first default-branch rescan closed #5, #6, and #7 and then identified
|
||||||
|
test-only path checks as High #8, #9, and #10 in the new SDK regression.
|
||||||
|
Replace every test-side `stat`/`lstat` sequence with one no-follow `open`
|
||||||
|
followed by `fstat` and descriptor reads, and retain that boundary in the
|
||||||
|
static regression gate. Default-branch run `33452563116` completed
|
||||||
|
successfully with #5 through #10 fixed and zero open code-scanning alerts.
|
||||||
|
- Treat inability to create the raw-C1 scanner filename as an explicit fixture
|
||||||
|
skip on filesystems that reject the byte; when creation succeeds, the unsafe
|
||||||
|
diagnostic-escaping assertions still run unchanged. Normalize Bash 3.2's
|
||||||
|
sign-extended `%d` character conversion to an unsigned octet so raw and
|
||||||
|
UTF-8 C1 diagnostics retain their canonical `\\xNN`/`\\uNNNN` form.
|
||||||
|
- Reject redirected or otherwise non-console Windows password prompts before
|
||||||
|
entering `_getch`, handle console EOF as an error, and cover the native
|
||||||
|
redirected-input path so it cannot hang a release gate.
|
||||||
|
- On Windows, validate private-key confinement as the protected,
|
||||||
|
current-user-only DACL that the implementation creates; do not treat MSYS's
|
||||||
|
synthetic `stat` mode as a POSIX `0600` result. POSIX continues to require
|
||||||
|
the real descriptor mode `0600` under multiple umasks.
|
||||||
|
- Replace the old MSYS `grep` non-BMP boundary exposed after 14 successful jobs
|
||||||
|
in pre-tag run `33452602634` with byte-exact Python checks for Latin-1, BMP,
|
||||||
|
and non-BMP UTF-8 listing output. Construct the emoji fixture from ASCII byte
|
||||||
|
escapes and require list, extract, and full-tree equality; the independent
|
||||||
|
path regression also creates its BMP/non-BMP name from ASCII hex.
|
||||||
|
- Run `sdk-test` from both `release-check` and the hosted GCC/Clang Linux job so
|
||||||
|
the atomic key-save regression cannot silently fall outside release gates.
|
||||||
|
- Carry the v5.2.7 archive format, cryptography, bundled codec release, and SDK
|
||||||
|
ABI forward unchanged. These are implementation and test-integration
|
||||||
|
corrections, not a wire-format or API change.
|
||||||
|
- Realign current code, package, workflow, artifact, and documentation
|
||||||
|
references to 5.2.8, and pin the AUR/Homebrew SHA-256 and Guix content hash
|
||||||
|
to the final reproducible source archive before tagging.
|
||||||
|
- Exact-tag run `33456209269` passed all 15 source, package, analyzer,
|
||||||
|
sanitizer, OBS, Windows, and macOS jobs at `ebb9ab3`. Promotion initially
|
||||||
|
stopped before publication because its validator treated `%{ARCH}` as the
|
||||||
|
SRPM discriminator; the genuine SRPMs instead correctly carry
|
||||||
|
`%{SOURCEPACKAGE}=1`. Commit `33eb904` corrected and strengthened that gate,
|
||||||
|
and run `33457868306` published the exact 13 tested assets without rebuilding
|
||||||
|
them. No v5.2.7 result transfers to this evidence.
|
||||||
|
|
||||||
|
## [5.2.7] — 2026-08-31 — Native test-harness portability corrections
|
||||||
|
|
||||||
|
Corrective successor to the immutable, unpromoted `v5.2.6` candidate. Exact-tag
|
||||||
|
GitHub Actions run `33442264243` completed 13 jobs successfully, but the native
|
||||||
|
Windows and macOS jobs failed, so no 5.2.6 assets were promoted. The tag and its
|
||||||
|
recorded evidence remain unchanged.
|
||||||
|
|
||||||
|
- Scope the SHA-NI regression helper declarations to the supported x86 build
|
||||||
|
path so macOS arm64 strict compilation does not diagnose unused declarations
|
||||||
|
under `-Werror`.
|
||||||
|
- Carry the safe printable UTF-8 path fixture through an explicit
|
||||||
|
byte-stable Windows argument representation so argv transcoding cannot abort
|
||||||
|
the regression before its intended archive and diagnostic assertions.
|
||||||
|
- Carry the 5.2.6 source-only and security baseline forward without changing
|
||||||
|
the archive format, cryptography, bundled codec release, or SDK ABI.
|
||||||
|
- Realign current source, package, workflow, artifact, and tag references to
|
||||||
|
5.2.7, then pin the final reproducible release-archive checksum and content
|
||||||
|
hash in the downstream recipes before tagging.
|
||||||
|
- Require fresh exact-`v5.2.7` source, checksum, hosted CI, native-platform,
|
||||||
|
package, OBS, and promotion evidence. No prior candidate result transfers
|
||||||
|
automatically, and this entry does not claim those gates passed.
|
||||||
|
|
||||||
|
## [5.2.6] — 2026-08-31 — Native release-gate portability corrections
|
||||||
|
|
||||||
|
Corrective successor to the immutable, unpromoted `v5.2.5` candidate. Exact-tag
|
||||||
|
GitHub Actions run `33434986357` completed 13 jobs successfully, but the native
|
||||||
|
Windows and macOS jobs failed, so no 5.2.5 assets were promoted. The tag and its
|
||||||
|
recorded evidence remain unchanged.
|
||||||
|
|
||||||
|
- Use the compiler-resistant volatile wipe fallback on macOS and NetBSD instead
|
||||||
|
of assuming that their C libraries export `explicit_bzero`; supported glibc,
|
||||||
|
FreeBSD, and OpenBSD paths retain their existing selection.
|
||||||
|
- Make the source-only scanner's empty-array handling compatible with the
|
||||||
|
system Bash 3.2 shipped by macOS, including repository, tag, standalone-tree,
|
||||||
|
standalone-archive, and path-component traversal paths.
|
||||||
|
- Make hostile archive-path fixtures accept explicit hexadecimal bytes, verify
|
||||||
|
the requested path bytes in the generated archive, and reject the dangerous
|
||||||
|
raw byte fragment anywhere in diagnostic output, so the Windows regression
|
||||||
|
does not depend on command-line conversion or benign path prefixes.
|
||||||
|
- Carry the 5.2.5 source-only and security baseline forward without changing
|
||||||
|
the archive format, cryptography, bundled codec release, or SDK ABI.
|
||||||
|
- Realign current package, workflow, artifact, and tag references to 5.2.6;
|
||||||
|
leave release-archive checksums explicitly pending until the final source
|
||||||
|
archive is generated.
|
||||||
|
- Require fresh exact-`v5.2.6` source, checksum, hosted CI, native-platform,
|
||||||
|
package, OBS, and promotion evidence. No prior candidate result transfers
|
||||||
|
automatically, and this changelog entry does not claim those gates passed.
|
||||||
|
|
||||||
|
## [5.2.5] — 2026-08-31 — OBS service working-directory correction
|
||||||
|
|
||||||
|
Corrective successor to the immutable `v5.2.4` candidate. GitHub Actions run
|
||||||
|
`33431386002` did not promote that tag: 12 jobs passed, the openSUSE Tumbleweed
|
||||||
|
RPM gate failed because its standalone `Serviceinfo` executor inherited the
|
||||||
|
repository working directory instead of the isolated service directory, and
|
||||||
|
the dependent Windows/macOS gate was skipped. The tag and its recorded
|
||||||
|
checksums remain unchanged.
|
||||||
|
|
||||||
|
- Run the standalone OBS `obs_scm` → `tar` → `recompress` chain from its
|
||||||
|
isolated service directory so downstream services can find `.obsinfo`.
|
||||||
|
- Add a packaging-policy regression for the OBS executor working directory
|
||||||
|
while retaining the immutable `refs/tags/v5.2.5` service revision assertion.
|
||||||
|
- Record the successful local Tumbleweed reproduction of the corrected service
|
||||||
|
chain; it produced one source archive that passed the source-only scanner.
|
||||||
|
- Carry the 5.2.4 security and source-only baseline forward without changing
|
||||||
|
the archive format, cryptography, bundled codec release, or SDK ABI.
|
||||||
|
- Realign current package, workflow, artifact, and tag references to 5.2.5;
|
||||||
|
leave AUR, Homebrew, and Guix hashes pending the final source archive.
|
||||||
|
- Require fresh exact-`v5.2.5` source, checksum, hosted CI, native-platform,
|
||||||
|
package, and promotion evidence; no v5.2.4 result transfers automatically.
|
||||||
|
|
||||||
|
## [5.2.4] — 2026-08-31 — Source-policy line-ending correction
|
||||||
|
|
||||||
|
Corrective successor to the immutable `v5.2.3` candidate. That candidate was
|
||||||
|
not promoted because its source-policy test assumed LF bytes for a Windows
|
||||||
|
`.bat` file that Git correctly materializes as CRLF according to
|
||||||
|
`.gitattributes`; the tag and its historical record remain unchanged.
|
||||||
|
|
||||||
|
- Correct the release/source-policy integration so the required CRLF checkout
|
||||||
|
form is validated without treating it as source drift.
|
||||||
|
- Carry the 5.2.3 release-integration work and the 5.2.2 security/source-only
|
||||||
|
baseline forward without an archive-format, cryptographic, codec, or SDK ABI
|
||||||
|
change.
|
||||||
|
- Realign current documentation, package, workflow, artifact, and tag references
|
||||||
|
to 5.2.4 while keeping generated binaries outside Git and source archives.
|
||||||
|
- Correct three internal regression-test headers so pre-3.0 product releases
|
||||||
|
retain the historical ZUPT name; VaptVupt remains only the bundled codec and
|
||||||
|
compatibility-facing identifier where applicable.
|
||||||
|
- Require fresh exact-`v5.2.4` source, checksum, hosted CI, native-platform,
|
||||||
|
package, and promotion evidence; no 5.2.3 result transfers automatically.
|
||||||
|
- Keep authenticated OBS/Factory validation pending and retain the unresolved,
|
||||||
|
unsuppressed openSUSE automatic `debugsource` rpmlint `no-binary` finding.
|
||||||
|
|
||||||
## [5.2.3] — 2026-08-31 — Corrective release integration
|
## [5.2.3] — 2026-08-31 — Corrective release integration
|
||||||
|
|
||||||
Corrective successor to the immutable `v5.2.2` candidate. Post-tag CI
|
Corrective successor to the immutable `v5.2.2` candidate. Post-tag CI
|
||||||
|
|
@ -2341,7 +2498,7 @@ bump and packaging-syntax test expansion; archive format unchanged.
|
||||||
### `.github/workflows/ci.yml` — 8-job CI matrix
|
### `.github/workflows/ci.yml` — 8-job CI matrix
|
||||||
|
|
||||||
Replaces the prior 4-job CI with a comprehensive matrix that mirrors
|
Replaces the prior 4-job CI with a comprehensive matrix that mirrors
|
||||||
the project's local-verification protocol from `PROMPT.md §6`:
|
the project's historical local-verification protocol:
|
||||||
|
|
||||||
| Job | What it does |
|
| Job | What it does |
|
||||||
|---|---|
|
|---|---|
|
||||||
|
|
@ -3064,7 +3221,7 @@ size to run. It needs to be a sprint-protocol step, not a one-off.
|
||||||
|
|
||||||
### What changed
|
### What changed
|
||||||
|
|
||||||
**`PROMPT.md` — Prompt v2.**
|
**Historical sprint instructions — version 2.**
|
||||||
|
|
||||||
- **NEW §3.5: The exhaustive byte-sweep mandate.** Every
|
- **NEW §3.5: The exhaustive byte-sweep mandate.** Every
|
||||||
format-touching change runs the full byte sweep before claiming
|
format-touching change runs the full byte sweep before claiming
|
||||||
|
|
@ -3084,7 +3241,7 @@ size to run. It needs to be a sprint-protocol step, not a one-off.
|
||||||
with the regression-test names that catch each one. The table is
|
with the regression-test names that catch each one. The table is
|
||||||
the canonical "things that have shipped and must never recur"
|
the canonical "things that have shipped and must never recur"
|
||||||
reference; keeping it current is part of every sprint.
|
reference; keeping it current is part of every sprint.
|
||||||
- **Footer stamp**: Prompt v2, 2026-05-20.
|
- **Footer stamp**: historical instruction revision 2, 2026-05-20.
|
||||||
|
|
||||||
**`Makefile`.** The help-target banner version is now derived
|
**`Makefile`.** The help-target banner version is now derived
|
||||||
from `include/zupt.h` via a `grep | awk` substitution:
|
from `include/zupt.h` via a `grep | awk` substitution:
|
||||||
|
|
@ -3095,7 +3252,7 @@ help:
|
||||||
include/zupt.h | awk -F'"' '{print $$2}') build targets:"
|
include/zupt.h | awk -F'"' '{print $$2}') build targets:"
|
||||||
```
|
```
|
||||||
|
|
||||||
This closes a recurring bug noted in `PROMPT.md §6 step 9` —
|
This closes a recurring bug noted in the historical sprint checklist —
|
||||||
prior sprints (2.3.0, 2.3.1) left the banner stale even after the
|
prior sprints (2.3.0, 2.3.1) left the banner stale even after the
|
||||||
sprint protocol said to bump it. Making it auto-derived removes
|
sprint protocol said to bump it. Making it auto-derived removes
|
||||||
the drift opportunity entirely.
|
the drift opportunity entirely.
|
||||||
|
|
@ -3126,7 +3283,6 @@ the drift opportunity entirely.
|
||||||
### Files touched
|
### Files touched
|
||||||
|
|
||||||
```
|
```
|
||||||
PROMPT.md (§3.5 NEW, §6 step renumber, §10 kickoff, §11 rows, v2 stamp)
|
|
||||||
Makefile (help banner auto-derives version from header)
|
Makefile (help banner auto-derives version from header)
|
||||||
include/zupt.h (version 2.3.1 → 2.4.0)
|
include/zupt.h (version 2.3.1 → 2.4.0)
|
||||||
CHANGELOG.md (this entry)
|
CHANGELOG.md (this entry)
|
||||||
|
|
@ -3310,7 +3466,7 @@ the footer.
|
||||||
|
|
||||||
- **Encrypted modes**: AIT = `HMAC-SHA256(mac_key, hdr[0..63] || footer[0..23])`
|
- **Encrypted modes**: AIT = `HMAC-SHA256(mac_key, hdr[0..63] || footer[0..23])`
|
||||||
- **Plaintext modes**: AIT = `XXH64(...)` in the first 8 bytes, zeros in
|
- **Plaintext modes**: AIT = `XXH64(...)` in the first 8 bytes, zeros in
|
||||||
the rest. Best-effort (`OPAQUE`-class per PROMPT.md §5).
|
the rest. Best-effort (`OPAQUE` structural-integrity class).
|
||||||
|
|
||||||
The MAC input deliberately excludes `footer[24..31]` = `"ZEND" || u32 version`.
|
The MAC input deliberately excludes `footer[24..31]` = `"ZEND" || u32 version`.
|
||||||
Both are structurally validated by the read path (`locate_footer_v15`
|
Both are structurally validated by the read path (`locate_footer_v15`
|
||||||
|
|
@ -3500,7 +3656,7 @@ so flipping it (e.g. from `0x02 INDEX` to `0x03 ENC_HEADER`) did
|
||||||
not cause auth failure; the downstream parser was tolerant.
|
not cause auth failure; the downstream parser was tolerant.
|
||||||
Severity: low. Fix at `src/zupt_format.c` adds the structural
|
Severity: low. Fix at `src/zupt_format.c` adds the structural
|
||||||
check immediately after `read_block`. This makes the byte
|
check immediately after `read_block`. This makes the byte
|
||||||
`OPAQUE`-class per PROMPT.md §5 (tamper detected by parser, not by
|
`OPAQUE` structural-integrity class (tamper detected by parser, not by
|
||||||
MAC).
|
MAC).
|
||||||
|
|
||||||
**F-02b — RECLASSIFIED.** The 2.2.4 hypothesis that the archive
|
**F-02b — RECLASSIFIED.** The 2.2.4 hypothesis that the archive
|
||||||
|
|
@ -3537,7 +3693,7 @@ other v2.3.0 changes than as a standalone patch.
|
||||||
### Verification
|
### Verification
|
||||||
|
|
||||||
- `make` — clean on plain GCC and Clang.
|
- `make` — clean on plain GCC and Clang.
|
||||||
- `make` with strict GCC flags (full set from PROMPT.md §6) — clean.
|
- `make` with the historical strict GCC warning set — clean.
|
||||||
- `make` with strict Clang flags — clean.
|
- `make` with strict Clang flags — clean.
|
||||||
- `make test` — **61/61 passing**.
|
- `make test` — **61/61 passing**.
|
||||||
- `make test-vectors` — **14/14 passing**.
|
- `make test-vectors` — **14/14 passing**.
|
||||||
|
|
@ -3573,8 +3729,8 @@ SECURITY.md (integrity statement reaffirmed)
|
||||||
|
|
||||||
Patch release. No format changes, no feature changes, no on-disk
|
Patch release. No format changes, no feature changes, no on-disk
|
||||||
compatibility impact. Five findings closed against the v2.2.3 baseline
|
compatibility impact. Five findings closed against the v2.2.3 baseline
|
||||||
under the methodology in the new top-level `PROMPT.md` (continuous
|
under the methodology in the then-current audit instructions and tracked in
|
||||||
improvement prompt) and tracked in `docs/FINDINGS-2.x.md` (durable
|
`docs/FINDINGS-2.x.md` (durable
|
||||||
numbered ledger that survives between work sessions).
|
numbered ledger that survives between work sessions).
|
||||||
|
|
||||||
### Findings closed
|
### Findings closed
|
||||||
|
|
@ -3646,9 +3802,9 @@ preserved.
|
||||||
|
|
||||||
### New process artefacts
|
### New process artefacts
|
||||||
|
|
||||||
- **`PROMPT.md`** — top-level "god-tier" continuous-improvement prompt.
|
- **Historical continuous-improvement instructions** — removed from the
|
||||||
Designed to be pasted verbatim into a fresh chat alongside the latest
|
current source tree after their durable material was consolidated into the
|
||||||
source tarball. Encodes the methodology that produced this release:
|
audit and security documents. They encoded the methodology that produced this release:
|
||||||
three-line workflow (survey → fix-with-test → ship), explicit
|
three-line workflow (survey → fix-with-test → ship), explicit
|
||||||
authentication-coverage invariant (every archive byte covered by
|
authentication-coverage invariant (every archive byte covered by
|
||||||
per-block HMAC OR a separate index MAC OR a footer MAC — no third
|
per-block HMAC OR a separate index MAC OR a footer MAC — no third
|
||||||
|
|
@ -3696,7 +3852,7 @@ preserved.
|
||||||
### Files touched
|
### Files touched
|
||||||
|
|
||||||
```
|
```
|
||||||
PROMPT.md (new, top-level)
|
[historical sprint-instruction file] (removed from the current tree)
|
||||||
docs/FINDINGS-2.x.md (new)
|
docs/FINDINGS-2.x.md (new)
|
||||||
tests/test_audit_flake.sh (new)
|
tests/test_audit_flake.sh (new)
|
||||||
src/zupt_main.c (F-01)
|
src/zupt_main.c (F-01)
|
||||||
|
|
@ -3808,20 +3964,20 @@ Two `make test` runs back-to-back, both clean. Cumulative test count:
|
||||||
|
|
||||||
### Documentation cleanup
|
### Documentation cleanup
|
||||||
|
|
||||||
Four design/audit-prompt documents that were sprint-internal scratch
|
Four design/audit-instruction documents that were sprint-internal scratch
|
||||||
have been removed from the source tree (consolidated into the
|
have been removed from the source tree (consolidated into the
|
||||||
remaining permanent docs):
|
remaining permanent docs):
|
||||||
|
|
||||||
| Removed | Where the content lives now |
|
| Removed | Where the content lives now |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `AUDIT_PROMPT.md` | superseded by `FORMAL_AUDIT_PROMPT.md` |
|
| Two historical audit-instruction files | consolidated into the permanent audit and security documents |
|
||||||
| `ROOT_CAUSE_ANALYSIS.md` | reproducible-bug postmortems are now per-release entries in `CHANGELOG.md` |
|
| `ROOT_CAUSE_ANALYSIS.md` | reproducible-bug postmortems are now per-release entries in `CHANGELOG.md` |
|
||||||
| `COMPAT.md` | the table moved into `README.md` § "Architecture & platform support" |
|
| `COMPAT.md` | the table moved into `README.md` § "Architecture & platform support" |
|
||||||
| `DONATIONS.md` | one-liner moved into `README.md` § "Supporting Zupt" |
|
| `DONATIONS.md` | one-liner moved into `README.md` § "Supporting Zupt" |
|
||||||
|
|
||||||
Surviving canonical docs: `README.md`, `CHANGELOG.md` (this file),
|
Surviving canonical docs: `README.md`, `CHANGELOG.md` (this file),
|
||||||
`SECURITY.md`, `INSTALL.md`, `LICENSE`, `THIRD-PARTY-NOTICES.md`,
|
`SECURITY.md`, `INSTALL.md`, `LICENSE`, `THIRD-PARTY-NOTICES.md`,
|
||||||
`AUDIT.md`, `FORMAL_AUDIT_PROMPT.md`, `ROADMAP.md`.
|
`AUDIT.md` and the then-current roadmap.
|
||||||
|
|
||||||
|
|
||||||
## [2.2.2-final2] — 2026-05-01 — CLI help, man pages, deb copyright
|
## [2.2.2-final2] — 2026-05-01 — CLI help, man pages, deb copyright
|
||||||
|
|
@ -3999,7 +4155,7 @@ the github-old → github-new migration sprint) deleted as obsolete.
|
||||||
|
|
||||||
## [2.2.2] god-tier audit — bug #16 (block-swap attack) fix
|
## [2.2.2] god-tier audit — bug #16 (block-swap attack) fix
|
||||||
|
|
||||||
Independent formal cryptographic audit (per FORMAL_AUDIT_PROMPT.md two-pass
|
Independent formal cryptographic audit (using the then-current two-pass
|
||||||
methodology) discovered a critical authenticated-encryption flaw in the
|
methodology) discovered a critical authenticated-encryption flaw in the
|
||||||
shipped 2.2.2 binary. Investigation, root-cause, fix, regression test, and
|
shipped 2.2.2 binary. Investigation, root-cause, fix, regression test, and
|
||||||
final verification documented below.
|
final verification documented below.
|
||||||
|
|
@ -4198,7 +4354,7 @@ Compile-tested with `-Wpedantic` under GCC. Win32 code paths verified via
|
||||||
- `AUDIT.md`: 2026-04-27 formal audit entry with cumulative test table
|
- `AUDIT.md`: 2026-04-27 formal audit entry with cumulative test table
|
||||||
- `README.md`: Security section bumped with audit confirmation
|
- `README.md`: Security section bumped with audit confirmation
|
||||||
- `doc/zupt.1`: SECURITY section mentions path-traversal protection
|
- `doc/zupt.1`: SECURITY section mentions path-traversal protection
|
||||||
- `FORMAL_AUDIT_PROMPT.md`: methodology document at repo root for future audits
|
- the then-current formal-audit methodology document
|
||||||
|
|
||||||
## [2.2.2] — 2026-04-27
|
## [2.2.2] — 2026-04-27
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Distributing ZUPT 5.2.3
|
# Distributing ZUPT 5.2.8
|
||||||
|
|
||||||
This document describes the packaging material maintained in the ZUPT
|
This document describes the packaging material maintained in the ZUPT
|
||||||
source repository. A recipe in `packaging/` is not evidence that a package has
|
source repository. A recipe in `packaging/` is not evidence that a package has
|
||||||
|
|
@ -14,9 +14,40 @@ https://github.com/cristiancmoises/zupt
|
||||||
GitHub is the canonical source and release host. Packaging must never fetch
|
GitHub is the canonical source and release host. Packaging must never fetch
|
||||||
`zupt-web` or substitute an asset from another project.
|
`zupt-web` or substitute an asset from another project.
|
||||||
|
|
||||||
The `v5.2.2` tag is immutable but remained a non-promoted candidate after
|
The `v5.2.2`, `v5.2.3`, `v5.2.4`, `v5.2.5`, `v5.2.6`, and `v5.2.7` tags are
|
||||||
post-tag CI integration failures. Corrective packages and release assets must
|
immutable non-promoted candidates.
|
||||||
use `v5.2.3`; never move or overwrite the 5.2.2 tag or its checksums.
|
The v5.2.3 source-policy test assumed LF for a Windows `.bat` file that Git
|
||||||
|
correctly checks out as CRLF. Exact-tag GitHub Actions run `33431386002` then
|
||||||
|
recorded 12 successful v5.2.4 jobs, one openSUSE service-harness failure caused
|
||||||
|
by its working directory, and skipped dependent Windows/macOS jobs. A local
|
||||||
|
Tumbleweed reproduction confirmed that `refs/tags/v5.2.4` is valid and that
|
||||||
|
entering the service directory completes the source-service chain. Corrective
|
||||||
|
working-directory integration was carried by v5.2.5, whose exact-tag GitHub
|
||||||
|
Actions run `33434986357` completed 13 jobs successfully but failed the native
|
||||||
|
Windows and macOS jobs. Its v5.2.6 corrections reached exact-tag run
|
||||||
|
`33442264243`, where 13 jobs succeeded but macOS arm64 failed on unused x86
|
||||||
|
SHA-NI test-helper declarations under `-Werror`, and Windows aborted during safe
|
||||||
|
UTF-8 fixture argv transcoding. Version 5.2.7 corrected those failures, but
|
||||||
|
exact-tag run `33445470664` ended with 13 successful jobs, a macOS raw-C1
|
||||||
|
fixture failure with `EILSEQ`, and a cancelled Windows job after the hosted job
|
||||||
|
stalled in `make check`; a MinGW/Wine reproduction isolated a non-console
|
||||||
|
password-prompt hang in `_getch`. Manual 5.2.8 pre-tag run `33452602634`
|
||||||
|
subsequently passed 14 of 15 jobs, including native macOS and the complete
|
||||||
|
Windows distribution checks, before an old MSYS `grep` non-BMP pattern failed
|
||||||
|
in the later smoke. ZUPT's redirected listing was byte-correct; the corrected
|
||||||
|
gate uses byte-exact, locale-independent checks and requires extraction plus a
|
||||||
|
full tree diff. The failed run is diagnostic evidence only.
|
||||||
|
Exact-tag run `33456209269` subsequently passed all 15 jobs at
|
||||||
|
`ebb9ab3aa1d42c50030ca02883f6162dc4771fe1`, including the pinned local OBS
|
||||||
|
source-service chain, native
|
||||||
|
Windows/macOS, and every package gate. Promotion run `33457868306` published
|
||||||
|
the exact tested 13-file set; the source archive SHA-256 is
|
||||||
|
`378b9506211545b9594cf0d38ac8955d9b1cac34eb6b379ae0ec26b84edb65f7`.
|
||||||
|
Corrective packages and release assets use `v5.2.8`; never move or
|
||||||
|
overwrite an earlier tag or checksum, and never transfer prior evidence
|
||||||
|
automatically. Version 5.2.8 corrects those native test boundaries, hardens
|
||||||
|
three path-race boundaries, and adds the SDK regression to release/hosted Linux
|
||||||
|
gates. The archive format, cryptography, codec, and SDK ABI remain unchanged.
|
||||||
|
|
||||||
## Source-only boundary
|
## Source-only boundary
|
||||||
|
|
||||||
|
|
@ -46,7 +77,7 @@ Audit the current tree or a generated archive with:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
scripts/check-source-only.sh
|
scripts/check-source-only.sh
|
||||||
scripts/check-source-only.sh --archive /path/to/zupt-5.2.3.tar.gz
|
scripts/check-source-only.sh --archive /path/to/zupt-5.2.8.tar.gz
|
||||||
```
|
```
|
||||||
|
|
||||||
The scanner reports paths, not file contents, and exits nonzero on a violation.
|
The scanner reports paths, not file contents, and exits nonzero on a violation.
|
||||||
|
|
@ -59,14 +90,15 @@ the result before moving it to its destination. Exporting the tree rather than
|
||||||
the commit omits Git's commit-ID PAX header:
|
the commit omits Git's commit-ID PAX header:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
SOURCE_DATE_EPOCH="$(git show -s --format=%ct HEAD)" \
|
make DIST_TARBALL=/tmp/zupt-5.2.8.tar.gz dist
|
||||||
make DIST_TARBALL=/tmp/zupt-5.2.3.tar.gz dist
|
sha256sum /tmp/zupt-5.2.8.tar.gz
|
||||||
sha256sum /tmp/zupt-5.2.3.tar.gz
|
|
||||||
```
|
```
|
||||||
|
|
||||||
With identical committed input and `SOURCE_DATE_EPOCH`, repeated exports must
|
The canonical release uses the tracked `.source-date-epoch`; an explicit
|
||||||
have the same SHA-256 digest. Do not generate a release tarball from uncommitted
|
`SOURCE_DATE_EPOCH` override intentionally creates a different archive. With
|
||||||
working-tree files.
|
identical committed input and epoch, repeated exports must have the same
|
||||||
|
SHA-256 digest. Do not generate a release tarball from uncommitted working-tree
|
||||||
|
files.
|
||||||
|
|
||||||
The AUR, Homebrew, and Guix recipes pin the checksum of this tarball. They are
|
The AUR, Homebrew, and Guix recipes pin the checksum of this tarball. They are
|
||||||
marked `export-ignore` in `.gitattributes` so their own checksum fields do not
|
marked `export-ignore` in `.gitattributes` so their own checksum fields do not
|
||||||
|
|
@ -107,12 +139,12 @@ private-library RPATH.
|
||||||
| openSUSE / OBS | `packaging/opensuse/` | source and binary RPM through OBS |
|
| openSUSE / OBS | `packaging/opensuse/` | source and binary RPM through OBS |
|
||||||
| Debian / Ubuntu | `packaging/debian/`, `packaging/build-deb.sh` | Debian metadata and binary DEB after the target gate |
|
| Debian / Ubuntu | `packaging/debian/`, `packaging/build-deb.sh` | Debian metadata and binary DEB after the target gate |
|
||||||
| RPM release artifact | `packaging/opensuse/zupt.spec`, `packaging/build-rpm.sh` | source and binary RPM after the target gate |
|
| RPM release artifact | `packaging/opensuse/zupt.spec`, `packaging/build-rpm.sh` | source and binary RPM after the target gate |
|
||||||
| GUI DEB | `packaging/build-gui-deb.sh` | `zupt-gui_5.2.3_all.deb` after payload/dependency and installed integration gates |
|
| GUI DEB | `packaging/build-gui-deb.sh` | `zupt-gui_5.2.8_all.deb` after payload/dependency and installed integration gates |
|
||||||
| GUI RPM | `packaging/build-gui-rpm.sh` | `zupt-gui-5.2.3-1.noarch.rpm` and matching `.src.rpm` after package and installed integration gates |
|
| GUI RPM | `packaging/build-gui-rpm.sh` | `zupt-gui-5.2.8-1.noarch.rpm` and matching `.src.rpm` after package and installed integration gates |
|
||||||
| Linux CLI archive | `.github/workflows/ci.yml` | `zupt-5.2.3-linux-x86_64.tar.xz` with notices after dependency, member, and extracted functional gates |
|
| Linux CLI archive | `.github/workflows/ci.yml` | `zupt-5.2.8-linux-x86_64.tar.xz` with notices after dependency, member, and extracted functional gates |
|
||||||
| Portable GUI source | `packaging/portable/`, `.github/workflows/ci.yml` | `zupt-gui-5.2.3-portable.zip` after source scan, member allowlist, and extracted off-screen integration gate |
|
| Portable GUI source | `packaging/portable/`, `.github/workflows/ci.yml` | `zupt-gui-5.2.8-portable.zip` after source scan, member allowlist, and extracted off-screen integration gate |
|
||||||
| Fedora / RPM-based systems | `packaging/rpm/zupt.spec` | downstream RPM starting point |
|
| Fedora / RPM-based systems | `packaging/rpm/zupt.spec` | downstream RPM starting point |
|
||||||
| AppImage helper | `packaging/build-appimage.sh` | downstream-only helper; no 5.2.3 AppImage is promoted |
|
| AppImage helper | `packaging/build-appimage.sh` | downstream-only helper; no 5.2.8 AppImage is promoted |
|
||||||
| Windows | `.github/workflows/cross-platform.yml` | native ZIP (executable plus notices) after the required native gate |
|
| Windows | `.github/workflows/cross-platform.yml` | native ZIP (executable plus notices) after the required native gate |
|
||||||
| macOS | `packaging/build-dmg.sh` | native-architecture DMG after the native gate |
|
| macOS | `packaging/build-dmg.sh` | native-architecture DMG after the native gate |
|
||||||
| Arch Linux | `packaging/aur/PKGBUILD` | AUR package recipe |
|
| Arch Linux | `packaging/aur/PKGBUILD` | AUR package recipe |
|
||||||
|
|
@ -188,17 +220,17 @@ expectations, then test the installed launcher off-screen against the matching
|
||||||
### Portable and native release artifacts
|
### Portable and native release artifacts
|
||||||
|
|
||||||
The Linux x86_64 gate packages the tested `zupt` executable as
|
The Linux x86_64 gate packages the tested `zupt` executable as
|
||||||
`zupt-5.2.3-linux-x86_64.tar.xz` beside README, changelog, security guidance,
|
`zupt-5.2.8-linux-x86_64.tar.xz` beside README, changelog, security guidance,
|
||||||
and every applicable public license and notice. Its dynamic-library allowlist,
|
and every applicable public license and notice. Its dynamic-library allowlist,
|
||||||
archive member allowlist, and extracted CLI functional suite must pass.
|
archive member allowlist, and extracted CLI functional suite must pass.
|
||||||
|
|
||||||
The `zupt-gui-5.2.3-portable.zip` artifact is source-only: it contains the GUI
|
The `zupt-gui-5.2.8-portable.zip` artifact is source-only: it contains the GUI
|
||||||
Python source, shell/macOS/Windows launchers, icons, provenance, changelog, and
|
Python source, shell/macOS/Windows launchers, icons, provenance, changelog, and
|
||||||
licenses, but no Python, Qt, CLI, or compiled runtime. The gate scans both the
|
licenses, but no Python, Qt, CLI, or compiled runtime. The gate scans both the
|
||||||
assembled and extracted trees, verifies an exact safe member allowlist, and
|
assembled and extracted trees, verifies an exact safe member allowlist, and
|
||||||
runs the extracted launcher off-screen against the tested CLI.
|
runs the extracted launcher off-screen against the tested CLI.
|
||||||
|
|
||||||
AppImage creation is deliberately offline and is not a 5.2.3 release gate.
|
AppImage creation is deliberately offline and is not a 5.2.8 release gate.
|
||||||
Supply a locally verified `appimagetool`, type-2 runtime, and the complete
|
Supply a locally verified `appimagetool`, type-2 runtime, and the complete
|
||||||
license/source-relink compliance notice for those exact runtime bytes; the
|
license/source-relink compliance notice for those exact runtime bytes; the
|
||||||
helper never downloads any input:
|
helper never downloads any input:
|
||||||
|
|
@ -214,7 +246,7 @@ APPIMAGE_RUNTIME_COMPLIANCE_FILE=/verified/path/runtime-compliance.txt \
|
||||||
The runtime inspected while preparing 5.2.2 omitted a linked component from
|
The runtime inspected while preparing 5.2.2 omitted a linked component from
|
||||||
its notice and did not provide the complete LGPL source/relink handoff required
|
its notice and did not provide the complete LGPL source/relink handoff required
|
||||||
by this release policy. No AppImage produced by this helper is promoted by the
|
by this release policy. No AppImage produced by this helper is promoted by the
|
||||||
upstream 5.2.3 workflow. AppDir and Flatpak bundles and GUI platform installers
|
upstream 5.2.8 workflow. AppDir and Flatpak bundles and GUI platform installers
|
||||||
are also excluded. Bare Linux and Windows executables are not promoted; their
|
are also excluded. Bare Linux and Windows executables are not promoted; their
|
||||||
CLI programs appear only inside notice-bearing archives. The Windows ZIP and
|
CLI programs appear only inside notice-bearing archives. The Windows ZIP and
|
||||||
macOS DMG remain CLI-only.
|
macOS DMG remain CLI-only.
|
||||||
|
|
@ -229,8 +261,8 @@ DIST_DIR="$release_dir" RUN_CHECKS=1 packaging/build-dmg.sh
|
||||||
The Windows ZIP (including its executable and notices) must be built and tested
|
The Windows ZIP (including its executable and notices) must be built and tested
|
||||||
by the Windows job in `.github/workflows/cross-platform.yml`; it is not a
|
by the Windows job in `.github/workflows/cross-platform.yml`; it is not a
|
||||||
cross-compiled release claim from a Linux build. No Wine result is retained as
|
cross-compiled release claim from a Linux build. No Wine result is retained as
|
||||||
5.2.3 release evidence. Extended-length/device namespace paths, raw UNC output
|
5.2.8 release evidence. Extended-length/device namespace paths, raw UNC output
|
||||||
roots, and mapped/network-drive output are not supported in 5.2.3. Publish the
|
roots, and mapped/network-drive output are not supported in 5.2.8. Publish the
|
||||||
exact architecture recorded by the native job.
|
exact architecture recorded by the native job.
|
||||||
These helpers create binary distribution artifacts for the release page, not
|
These helpers create binary distribution artifacts for the release page, not
|
||||||
content to be committed to Git or included in the source archive.
|
content to be committed to Git or included in the source archive.
|
||||||
|
|
@ -238,7 +270,7 @@ content to be committed to Git or included in the source archive.
|
||||||
### AUR, Homebrew, Guix, and Nix
|
### AUR, Homebrew, Guix, and Nix
|
||||||
|
|
||||||
After calculating the final reproducible source archive, but before creating or
|
After calculating the final reproducible source archive, but before creating or
|
||||||
publishing the immutable tag, update each recipe to version 5.2.3 and to the
|
publishing the immutable tag, update each recipe to version 5.2.8 and to the
|
||||||
exact digest or content hash expected by its package manager. These recipe
|
exact digest or content hash expected by its package manager. These recipe
|
||||||
directories are excluded from the source archive, so this does not create a
|
directories are excluded from the source archive, so this does not create a
|
||||||
checksum cycle. Commit the pinned recipes in the tagged tree, then build and
|
checksum cycle. Commit the pinned recipes in the tagged tree, then build and
|
||||||
|
|
@ -257,7 +289,7 @@ build.
|
||||||
|
|
||||||
For every published artifact:
|
For every published artifact:
|
||||||
|
|
||||||
1. start from the immutable `v5.2.3` tag;
|
1. start from the immutable `v5.2.8` tag;
|
||||||
2. keep `WITH_SDK=0 WITH_PQBOX=0` unless system dependencies are declared;
|
2. keep `WITH_SDK=0 WITH_PQBOX=0` unless system dependencies are declared;
|
||||||
3. record the exact OS, distribution release, architecture, and toolchain;
|
3. record the exact OS, distribution release, architecture, and toolchain;
|
||||||
4. run format validation plus installed `--version`, `--help`, and archive
|
4. run format validation plus installed `--version`, `--help`, and archive
|
||||||
|
|
@ -269,13 +301,14 @@ For every published artifact:
|
||||||
Do not infer multi-architecture compatibility from portable source. Do not add
|
Do not infer multi-architecture compatibility from portable source. Do not add
|
||||||
precompiled optional libraries to make a package build.
|
precompiled optional libraries to make a package build.
|
||||||
|
|
||||||
Publish release assets at the canonical GitHub release. If an expected asset is
|
The 13 gated assets are published at the
|
||||||
absent or has a different checksum, report that target as unpublished rather
|
[canonical GitHub release](https://github.com/cristiancmoises/zupt/releases/tag/v5.2.8).
|
||||||
than redirecting consumers to an unverified file.
|
If an expected asset is absent or has a different checksum, report that target
|
||||||
|
as unpublished rather than redirecting consumers to an unverified file.
|
||||||
|
|
||||||
## Downstream checklist
|
## Downstream checklist
|
||||||
|
|
||||||
- [ ] The source URL resolves to the immutable `v5.2.3` tag.
|
- [ ] The source URL resolves to the immutable `v5.2.8` tag.
|
||||||
- [ ] The source archive passes `scripts/check-source-only.sh --archive`.
|
- [ ] The source archive passes `scripts/check-source-only.sh --archive`.
|
||||||
- [ ] The recipe checksum matches the downloaded source exactly.
|
- [ ] The recipe checksum matches the downloaded source exactly.
|
||||||
- [ ] `WITH_SDK=0 WITH_PQBOX=0` is explicit, or system dependencies are complete.
|
- [ ] `WITH_SDK=0 WITH_PQBOX=0` is explicit, or system dependencies are complete.
|
||||||
|
|
|
||||||
41
INSTALL.md
41
INSTALL.md
|
|
@ -1,4 +1,4 @@
|
||||||
# Installing ZUPT 5.2.3
|
# Installing ZUPT 5.2.8
|
||||||
|
|
||||||
This guide covers the ZUPT command-line program and the optional Python GUI.
|
This guide covers the ZUPT command-line program and the optional Python GUI.
|
||||||
The canonical source repository is
|
The canonical source repository is
|
||||||
|
|
@ -16,14 +16,39 @@ The canonical source repository is
|
||||||
record a successful format-specific test for your target.
|
record a successful format-specific test for your target.
|
||||||
|
|
||||||
The immutable `v5.2.2` candidate was not promoted after CI integration
|
The immutable `v5.2.2` candidate was not promoted after CI integration
|
||||||
failures. Do not treat a 5.2.2 candidate artifact as a 5.2.3 package.
|
failures. The immutable `v5.2.3` candidate was not promoted because its
|
||||||
|
source-policy test assumed LF for a Windows `.bat` file checked out as the
|
||||||
|
required CRLF. The immutable `v5.2.4` candidate was not promoted after exact-tag
|
||||||
|
GitHub Actions run `33431386002`: 12 jobs succeeded, the sole openSUSE
|
||||||
|
service-harness job failed because its executor did not enter the service
|
||||||
|
directory, and dependent Windows/macOS jobs were skipped. A local Tumbleweed
|
||||||
|
reproduction confirmed both the explicit tag ref and the corrected
|
||||||
|
working-directory contract. This is release/test integration only; the product,
|
||||||
|
archive format, cryptography, codec, and SDK ABI are unchanged. The immutable
|
||||||
|
`v5.2.5` candidate was likewise not promoted: exact-tag GitHub Actions run
|
||||||
|
`33434986357` recorded 13 successful jobs and failed native Windows/macOS jobs.
|
||||||
|
The immutable `v5.2.6` candidate was not promoted after run `33442264243`
|
||||||
|
recorded 13 successful jobs and two native failures: unused x86 SHA-NI helper
|
||||||
|
declarations on macOS arm64 under `-Werror`, and early Windows abortion while
|
||||||
|
argv-transcoding a safe UTF-8 fixture. Version 5.2.7 corrected those boundaries
|
||||||
|
but was not promoted after exact-tag run `33445470664`: 13 jobs succeeded,
|
||||||
|
macOS failed because its filesystem rejected the raw-C1 filename fixture with
|
||||||
|
`EILSEQ`, and Windows was cancelled after the hosted job stalled in `make
|
||||||
|
check`; a MinGW/Wine reproduction isolated the cause to the non-console
|
||||||
|
password-prompt test entering `_getch`. Version 5.2.8 makes both
|
||||||
|
fixtures portable, hardens the three CodeQL High path-race boundaries described
|
||||||
|
in the security documents, and adds `sdk-test` to release and hosted Linux
|
||||||
|
gates. Exact-tag run `33456209269` passed all 15 jobs, and promotion run
|
||||||
|
`33457868306` published the exact tested set. Do not treat any prior candidate's
|
||||||
|
artifacts or evidence as 5.2.8 packages or validation.
|
||||||
|
|
||||||
The 5.2.3 package set eligible for promotion after each target gate succeeds is:
|
The published 5.2.8 package set is exactly these 13 gated assets:
|
||||||
|
|
||||||
| Component | Gated artifacts |
|
| Component | Gated artifacts |
|
||||||
|---|---|
|
|---|---|
|
||||||
| CLI | `zupt-5.2.3.tar.gz`, `zupt_5.2.3_amd64.deb`, openSUSE x86_64 binary/source RPMs, `zupt-5.2.3-linux-x86_64.tar.xz`, `zupt-5.2.3-windows-x86_64.zip`, and `ZUPT-5.2.3-macOS-*.dmg` |
|
| Source and checksums | `zupt-5.2.8.tar.gz`, `zupt-5.2.8.tar.gz.sha256`, and `SHA256SUMS` |
|
||||||
| GUI | `zupt-gui_5.2.3_all.deb`, `zupt-gui-5.2.3-1.noarch.rpm`, `zupt-gui-5.2.3-1.src.rpm`, and `zupt-gui-5.2.3-portable.zip` |
|
| CLI | `zupt_5.2.8_amd64.deb`, `zupt-5.2.8-0.x86_64.rpm`, `zupt-5.2.8-0.src.rpm`, `zupt-5.2.8-linux-x86_64.tar.xz`, `zupt-5.2.8-windows-x86_64.zip`, and exactly one `ZUPT-5.2.8-macOS-{x86_64\|arm64}.dmg` |
|
||||||
|
| GUI | `zupt-gui_5.2.8_all.deb`, `zupt-gui-5.2.8-1.noarch.rpm`, `zupt-gui-5.2.8-1.src.rpm`, and `zupt-gui-5.2.8-portable.zip` |
|
||||||
|
|
||||||
The GUI packages require the matching `zupt` CLI package and must pass exact
|
The GUI packages require the matching `zupt` CLI package and must pass exact
|
||||||
payload/dependency checks plus an installed off-screen GUI/CLI integration
|
payload/dependency checks plus an installed off-screen GUI/CLI integration
|
||||||
|
|
@ -31,7 +56,7 @@ test. The source-only portable GUI ZIP bundles launchers, notices, and GUI
|
||||||
source, but not Python, Qt, or the CLI. The Linux tar.xz carries the tested CLI
|
source, but not Python, Qt, or the CLI. The Linux tar.xz carries the tested CLI
|
||||||
beside the complete public license/notice payload. AppImage, AppDir, Flatpak
|
beside the complete public license/notice payload. AppImage, AppDir, Flatpak
|
||||||
bundles, GUI platform installers, and bare Linux/Windows executables are not
|
bundles, GUI platform installers, and bare Linux/Windows executables are not
|
||||||
promoted for 5.2.3. The Windows ZIP and macOS DMG contain the CLI only. Exact
|
promoted for 5.2.8. The Windows ZIP and macOS DMG contain the CLI only. Exact
|
||||||
target boundaries are listed in `README.md`.
|
target boundaries are listed in `README.md`.
|
||||||
The release's `SHA256SUMS` and validation notes, not the mere presence of a
|
The release's `SHA256SUMS` and validation notes, not the mere presence of a
|
||||||
download link, identify an artifact that completed its gate.
|
download link, identify an artifact that completed its gate.
|
||||||
|
|
@ -68,7 +93,7 @@ sudo pacman -S base-devel gzip
|
||||||
```
|
```
|
||||||
|
|
||||||
Package names can differ by distribution release. These commands are examples,
|
Package names can differ by distribution release. These commands are examples,
|
||||||
not a statement that 5.2.3 has been accepted into each distribution repository.
|
not a statement that 5.2.8 has been accepted into each distribution repository.
|
||||||
|
|
||||||
## Build and test from source
|
## Build and test from source
|
||||||
|
|
||||||
|
|
@ -88,7 +113,7 @@ From a release archive, run the scanner as follows before extraction or from a
|
||||||
trusted checkout after download:
|
trusted checkout after download:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
scripts/check-source-only.sh --archive /path/to/zupt-5.2.3.tar.gz
|
scripts/check-source-only.sh --archive /path/to/zupt-5.2.8.tar.gz
|
||||||
```
|
```
|
||||||
|
|
||||||
The default build provides the native password, ML-KEM-768 + X25519 hybrid
|
The default build provides the native password, ML-KEM-768 + X25519 hybrid
|
||||||
|
|
|
||||||
1
Makefile
1
Makefile
|
|
@ -540,6 +540,7 @@ test-all: check
|
||||||
# Release-only gates need a committed Git checkout and packaging metadata.
|
# Release-only gates need a committed Git checkout and packaging metadata.
|
||||||
# Keep them out of downstream %check, which intentionally has no dist rebuild.
|
# Keep them out of downstream %check, which intentionally has no dist rebuild.
|
||||||
release-check: test-all audit-licenses
|
release-check: test-all audit-licenses
|
||||||
|
$(Q)$(MAKE) sdk-test
|
||||||
$(Q)bash tests/test_static_analysis.sh
|
$(Q)bash tests/test_static_analysis.sh
|
||||||
$(Q)bash tests/test_packaging_syntax.sh
|
$(Q)bash tests/test_packaging_syntax.sh
|
||||||
$(Q)bash scripts/test-installed-zupt.sh ./$(TARGET)
|
$(Q)bash scripts/test-installed-zupt.sh ./$(TARGET)
|
||||||
|
|
|
||||||
216
README.md
216
README.md
|
|
@ -1,21 +1,113 @@
|
||||||
# ZUPT 5.2.3
|
# ZUPT 5.2.8
|
||||||
|
|
||||||
ZUPT is a command-line backup archiver written in C11. It combines the
|
ZUPT is a command-line backup archiver written in C11. It combines the
|
||||||
bundled VaptVupt compression codec with authenticated AES-256-CTR +
|
bundled VaptVupt compression codec with authenticated AES-256-CTR +
|
||||||
HMAC-SHA256 encryption, native ML-KEM-768/X25519 hybrid encryption, archive
|
HMAC-SHA256 encryption, native ML-KEM-768/X25519 hybrid encryption, archive
|
||||||
integrity checks, multithreaded operation, and a Python/Qt graphical frontend.
|
integrity checks, multithreaded operation, and a Python/Qt graphical frontend.
|
||||||
|
|
||||||
Version 5.2.3 is the corrective release for the source, package, workflow, and
|
Version 5.2.8 closes three CodeQL High path-race findings: SDK key copies now
|
||||||
release-integration path. The `v5.2.2` tag remains immutable, but its candidate
|
publish atomically through an already-open private object, POSIX disk restore
|
||||||
was not promoted after post-tag CI integration failures; no 5.2.2 binary set is
|
classifies and retains the descriptor it actually opened, and benchmark cleanup
|
||||||
presented as the current release.
|
traverses only pinned descriptors or handles without following links or Windows
|
||||||
|
reparse points. It also makes the raw-C1 scanner fixture explicitly skip a
|
||||||
|
filesystem that rejects creation with `EILSEQ`, normalizes Bash 3.2 signed-byte
|
||||||
|
diagnostics, and brings `sdk-test` into the
|
||||||
|
release and hosted Linux gates. Windows password prompts now reject redirected
|
||||||
|
input before entering `_getch` and treat console EOF as an error; its key-file
|
||||||
|
regression validates the protected current-user-only DACL rather than an MSYS
|
||||||
|
POSIX-mode projection. The C/C++ default-branch scan run `33452563116` of
|
||||||
|
commit `7a8e5c5` completed successfully after the follow-up changed the new SDK
|
||||||
|
regression to no-follow descriptors plus `fstat` and descriptor reads. Alerts
|
||||||
|
#5 through #10 are fixed, and the authenticated code-scanning API reported zero
|
||||||
|
open alerts. Final release-commit CodeQL run `33456049125` also completed
|
||||||
|
successfully, with the API still reporting zero open alerts. These corrections
|
||||||
|
do not change archive format v1.6,
|
||||||
|
cryptography, the bundled codec release, or the SDK ABI.
|
||||||
|
|
||||||
|
The predecessor `v5.2.7` tag is immutable and was not promoted. Exact-tag run
|
||||||
|
`33445470664` concluded `cancelled` at `2026-08-31T23:11:19Z`, with 13
|
||||||
|
successful jobs, a macOS raw-C1/EILSEQ fixture failure, and a cancelled Windows
|
||||||
|
job. The hosted Windows job stalled in `make check`; a MinGW/Wine reproduction
|
||||||
|
attributed the stall to a non-console password prompt entering `_getch`. No
|
||||||
|
v5.2.7 evidence transfers automatically to v5.2.8.
|
||||||
|
|
||||||
|
Manual pre-tag run `33452602634` then completed 14 of 15 jobs successfully at
|
||||||
|
`7a8e5c5`, including the native macOS DMG gate and the Windows build and full
|
||||||
|
distribution checks. Its Windows smoke test failed only when the old MSYS
|
||||||
|
`grep` matched a literal non-BMP filename after the product had already
|
||||||
|
compressed and verified all five files. MinGW/Wine reproduction confirmed the
|
||||||
|
exact `F0 9F 98 80` UTF-8 bytes in ZUPT's redirected listing. The corrected
|
||||||
|
gate creates that name from byte escapes, validates Latin-1, BMP, and non-BMP
|
||||||
|
listing bytes with Python, and requires extraction plus a full tree diff. The
|
||||||
|
failed run is diagnostic evidence, not release-candidate approval.
|
||||||
|
|
||||||
|
The immutable `v5.2.8` candidate at commit
|
||||||
|
`ebb9ab3aa1d42c50030ca02883f6162dc4771fe1` subsequently passed all 15 jobs in
|
||||||
|
manually dispatched exact-tag run `33456209269`. That run includes the pinned
|
||||||
|
local OBS source-service chain, reproducible source checks, GCC/Clang, analyzers,
|
||||||
|
sanitizers, DEB/RPM/SRPM and portable-package gates, the native Windows ZIP
|
||||||
|
round trip, and the mounted macOS arm64 DMG test. Corrected promotion run
|
||||||
|
`33457868306` validated and published exactly 13 assets. The canonical source
|
||||||
|
archive is 798296 bytes with SHA-256
|
||||||
|
`378b9506211545b9594cf0d38ac8955d9b1cac34eb6b379ae0ec26b84edb65f7`.
|
||||||
|
|
||||||
Version 5.2.2 restored the original ZUPT product name and the `zupt` command.
|
Version 5.2.2 restored the original ZUPT product name and the `zupt` command.
|
||||||
The `.zupt` archive extension, format v1.6, magic bytes, codec identifiers, and
|
The `.zupt` archive extension, format v1.6, magic bytes, codec identifiers, and
|
||||||
SDK ABI remain unchanged. An optional `vaptvupt` command alias may be provided
|
SDK ABI remain unchanged. An optional `vaptvupt` command alias may be provided
|
||||||
for scripts written against versions 3.0.0 through 5.2.1.
|
for scripts written against versions 3.0.0 through 5.2.1.
|
||||||
|
|
||||||
## Corrective changes in 5.2.3
|
## Corrective changes in 5.2.8
|
||||||
|
|
||||||
|
SDK key saves use atomic descriptor/handle-backed publication and preserve the
|
||||||
|
requested private/public modes without reopening the destination. Disk restore
|
||||||
|
opens a POSIX target once before its type, identity, and device-capacity
|
||||||
|
decisions, and benchmark cleanup is descriptor-relative on POSIX and
|
||||||
|
handle/reparse-point aware on Windows. The live-workspace symlink regression,
|
||||||
|
SDK link-target/mode regression, static path-race guards, portable raw-C1
|
||||||
|
fixture with Bash 3.2 unsigned-byte normalization, native redirected-prompt
|
||||||
|
and protected-DACL regressions, byte-exact BMP/non-BMP Windows list and extract
|
||||||
|
checks, and `sdk-test` CI step cover these boundaries. All current release
|
||||||
|
paths moved to 5.2.8 and received fresh exact-tag hosted CI, package,
|
||||||
|
native-platform, source-only, checksum, OBS, and promotion evidence in runs
|
||||||
|
`33456209269` and `33457868306`.
|
||||||
|
|
||||||
|
## Corrective changes introduced in 5.2.7
|
||||||
|
|
||||||
|
The SHA-NI regression keeps x86-only helper declarations out of unsupported
|
||||||
|
arm64 builds, and the safe UTF-8 Windows fixture crosses the argv boundary in a
|
||||||
|
byte-stable representation. Those test-harness changes did not alter the
|
||||||
|
archive format, cryptography, codec, or SDK ABI. Exact-tag run `33445470664`
|
||||||
|
subsequently exposed the separate macOS raw-C1/EILSEQ fixture failure; Windows
|
||||||
|
was cancelled after the hosted job stalled in `make check`; MinGW/Wine then
|
||||||
|
isolated the stall to a non-console password prompt entering `_getch`. The run recorded 13 successful jobs, one
|
||||||
|
failure, and one cancellation; v5.2.7 remained unpromoted.
|
||||||
|
|
||||||
|
## Corrective changes introduced in 5.2.6
|
||||||
|
|
||||||
|
Darwin and NetBSD select the portable compiler-resistant secure-wipe fallback;
|
||||||
|
scanner option/path arrays are guarded for Bash 3.2; and hostile Windows path
|
||||||
|
fixtures use explicit bytes and reject dangerous raw diagnostic fragments.
|
||||||
|
Those corrections changed release/test integration only. The resulting v5.2.6
|
||||||
|
candidate was not promoted because its next exact-tag run exposed the distinct
|
||||||
|
arm64 SHA-NI helper and safe UTF-8 Windows argv failures described above.
|
||||||
|
|
||||||
|
## Corrective changes introduced in 5.2.5
|
||||||
|
|
||||||
|
The exact-tag openSUSE gate executes its standalone service chain from the
|
||||||
|
directory containing `_service`. A local Tumbleweed reproduction confirmed
|
||||||
|
that `refs/tags/v5.2.4` resolves correctly and that entering the service
|
||||||
|
directory completes `obs_scm`, `tar`, and `recompress`. The immutable v5.2.4
|
||||||
|
candidate recorded 12 successful jobs in run `33431386002`; its openSUSE job
|
||||||
|
failed before the correction and dependent Windows/macOS jobs were skipped.
|
||||||
|
|
||||||
|
## Corrective changes introduced in 5.2.4
|
||||||
|
|
||||||
|
The release gate now validates the required CRLF checkout form without treating
|
||||||
|
it as source drift. That candidate required fresh exact-tag CI, package,
|
||||||
|
native-platform, source-only, and checksum evidence before promotion. The
|
||||||
|
`v5.2.3` tag remains immutable and unpromoted.
|
||||||
|
|
||||||
|
## Corrective changes introduced in 5.2.3
|
||||||
|
|
||||||
The corrective release carries the 5.2.2 security and format work forward
|
The corrective release carries the 5.2.2 security and format work forward
|
||||||
without a new archive format, codec, or SDK ABI. It realigns every current
|
without a new archive format, codec, or SDK ABI. It realigns every current
|
||||||
|
|
@ -98,8 +190,12 @@ See [CHANGELOG.md](CHANGELOG.md) for the release record.
|
||||||
## Canonical source
|
## Canonical source
|
||||||
|
|
||||||
- Canonical: https://github.com/cristiancmoises/zupt
|
- Canonical: https://github.com/cristiancmoises/zupt
|
||||||
|
- Codeberg mirror: https://codeberg.org/berkeley/zupt
|
||||||
|
- SecurityOps Brazil mirror: https://git.securityops.com.br/cristiancmoises/zupt
|
||||||
|
- SecurityOps global mirror: https://git.securityops.co/cristiancmoises/zupt
|
||||||
|
|
||||||
Release tags and source archives are published from this repository.
|
GitHub remains canonical. The `v5.2.8` tag and its 13 release assets are also
|
||||||
|
published byte-for-byte on the three mirrors above.
|
||||||
|
|
||||||
## Source-only policy
|
## Source-only policy
|
||||||
|
|
||||||
|
|
@ -113,10 +209,10 @@ users. Those assets must be built from the tagged source, tested on their target
|
||||||
environment, and kept outside Git and the source archive. A format that was not
|
environment, and kept outside Git and the source archive. A format that was not
|
||||||
built and tested is not presented as supported.
|
built and tested is not presented as supported.
|
||||||
|
|
||||||
## 5.2.3 release artifacts
|
## 5.2.8 release artifacts
|
||||||
|
|
||||||
The 5.2.3 release workflow is defined to produce the following files only after
|
The published 5.2.8 release contains exactly the following 13 files after
|
||||||
the corresponding target gate succeeds. `SHA256SUMS` records the exact promoted
|
every corresponding target gate succeeded. `SHA256SUMS` records the exact promoted
|
||||||
filenames and digests. The release notes identify the tested commit and the
|
filenames and digests. The release notes identify the tested commit and the
|
||||||
manually dispatched CI run; that run's job definitions and logs are the runtime
|
manually dispatched CI run; that run's job definitions and logs are the runtime
|
||||||
evidence for runner image, architecture, toolchain, results, and explicit
|
evidence for runner image, architecture, toolchain, results, and explicit
|
||||||
|
|
@ -124,23 +220,26 @@ skips. This table is not a substitute for that evidence.
|
||||||
|
|
||||||
| Format | Intended target and validation boundary |
|
| Format | Intended target and validation boundary |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `zupt-5.2.3.tar.gz` | Reproducible, source-only archive; scanned twice-built input plus SHA-256. |
|
| `zupt-5.2.8.tar.gz` | Reproducible, source-only archive; scanned twice-built input plus SHA-256. |
|
||||||
| `zupt_5.2.3_amd64.deb` | Ubuntu 24.04 amd64 package; install, functional round trip, and uninstall gate. |
|
| `zupt-5.2.8.tar.gz.sha256` | SHA-256 sidecar for the reproducible source archive. |
|
||||||
| `zupt-5.2.3-*.x86_64.rpm` and `.src.rpm` | openSUSE Tumbleweed x86_64 source/binary RPM gate; package inspection, install, round trip, and uninstall. |
|
| `zupt_5.2.8_amd64.deb` | Ubuntu 24.04 amd64 package; install, functional round trip, and uninstall gate. |
|
||||||
| `zupt-5.2.3-linux-x86_64.tar.xz` | Linux x86_64 CLI plus the complete public license/notice payload; dependency allowlist and extracted-package functional gate. |
|
| `zupt-5.2.8-0.x86_64.rpm` | openSUSE Tumbleweed x86_64 binary RPM; package inspection, install, round trip, and uninstall gate. |
|
||||||
| `zupt-gui_5.2.3_all.deb` | Architecture-independent Python/Qt GUI package; exact dependency/payload checks plus installed off-screen GUI/CLI integration gate. |
|
| `zupt-5.2.8-0.src.rpm` | Source RPM corresponding exactly to the gated openSUSE binary RPM. |
|
||||||
| `zupt-gui-5.2.3-1.noarch.rpm` | Architecture-independent Python/Qt GUI RPM; package inspection plus installed off-screen GUI/CLI integration gate. |
|
| `zupt-5.2.8-linux-x86_64.tar.xz` | Linux x86_64 CLI plus the complete public license/notice payload; dependency allowlist and extracted-package functional gate. |
|
||||||
| `zupt-gui-5.2.3-1.src.rpm` | Source RPM corresponding exactly to the gated noarch GUI RPM. |
|
| `zupt-gui_5.2.8_all.deb` | Architecture-independent Python/Qt GUI package; exact dependency/payload checks plus installed off-screen GUI/CLI integration gate. |
|
||||||
| `zupt-gui-5.2.3-portable.zip` | Source-only GUI and launchers with licenses/provenance; source scan, exact member allowlist, and extracted off-screen GUI/CLI gate. |
|
| `zupt-gui-5.2.8-1.noarch.rpm` | Architecture-independent Python/Qt GUI RPM; package inspection plus installed off-screen GUI/CLI integration gate. |
|
||||||
| `zupt-5.2.3-windows-x86_64.zip` | Native Windows x86_64 executable with notices; extracted-ZIP round-trip gate. |
|
| `zupt-gui-5.2.8-1.src.rpm` | Source RPM corresponding exactly to the gated noarch GUI RPM. |
|
||||||
| `ZUPT-5.2.3-macOS-*.dmg` | Native macOS image; mounted packaged executable round-trip gate, with the actual architecture in the filename. |
|
| `zupt-gui-5.2.8-portable.zip` | Source-only GUI and launchers with licenses/provenance; source scan, exact member allowlist, and extracted off-screen GUI/CLI gate. |
|
||||||
|
| `zupt-5.2.8-windows-x86_64.zip` | Native Windows x86_64 executable with notices; extracted-ZIP round-trip gate. |
|
||||||
|
| Exactly one `ZUPT-5.2.8-macOS-{x86_64\|arm64}.dmg` | Native macOS image; mounted packaged executable round-trip gate, with the actual runner architecture in the filename. |
|
||||||
|
| `SHA256SUMS` | Deterministic manifest covering the other 12 promoted files. |
|
||||||
|
|
||||||
An asset absent from the release was not promoted through its mandatory gate.
|
An asset absent from the release was not promoted through its mandatory gate.
|
||||||
Do not infer support for another distribution release, OS version, CPU
|
Do not infer support for another distribution release, OS version, CPU
|
||||||
architecture, raw UNC/SMB destination, or package manager from a similarly
|
architecture, raw UNC/SMB destination, or package manager from a similarly
|
||||||
named file. Binary assets are release outputs, never source-build inputs.
|
named file. Binary assets are release outputs, never source-build inputs.
|
||||||
|
|
||||||
No AppImage is promised for 5.2.3. The inspected upstream type-2 runtime lacked
|
No AppImage is promised for 5.2.8. The inspected upstream type-2 runtime lacked
|
||||||
a complete notice/source-relink handoff for every statically linked component,
|
a complete notice/source-relink handoff for every statically linked component,
|
||||||
so redistributing it would not meet this release's provenance gate. AppDir and
|
so redistributing it would not meet this release's provenance gate. AppDir and
|
||||||
Flatpak bundles and GUI platform installers are likewise outside the promoted
|
Flatpak bundles and GUI platform installers are likewise outside the promoted
|
||||||
|
|
@ -156,9 +255,10 @@ software already installed on the target. Other historical GUI packages and
|
||||||
platform installers are not carried forward implicitly.
|
platform installers are not carried forward implicitly.
|
||||||
|
|
||||||
The canonical source repository is
|
The canonical source repository is
|
||||||
<https://github.com/cristiancmoises/zupt>. Release assets referenced by the AUR,
|
<https://github.com/cristiancmoises/zupt>. The canonical release is
|
||||||
Homebrew, Guix, or generic RPM recipes must exist in the canonical GitHub
|
<https://github.com/cristiancmoises/zupt/releases/tag/v5.2.8>. Assets referenced
|
||||||
release at their recorded URL before those recipes are published.
|
by the AUR, Homebrew, Guix, or generic RPM recipes must exist there at their
|
||||||
|
recorded URL before those recipes are published.
|
||||||
|
|
||||||
Audit the current checkout and its Git archive with:
|
Audit the current checkout and its Git archive with:
|
||||||
|
|
||||||
|
|
@ -170,8 +270,8 @@ bash tests/test_source_only.sh
|
||||||
For a tag or an existing source archive:
|
For a tag or an existing source archive:
|
||||||
|
|
||||||
~~~sh
|
~~~sh
|
||||||
bash scripts/check-source-only.sh --tag v5.2.3
|
bash scripts/check-source-only.sh --tag v5.2.8
|
||||||
bash scripts/check-source-only.sh --archive /path/to/zupt-5.2.3.tar.gz
|
bash scripts/check-source-only.sh --archive /path/to/zupt-5.2.8.tar.gz
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
Unknown `.bin` files fail the scan. A necessary binary data fixture may be
|
Unknown `.bin` files fail the scan. A necessary binary data fixture may be
|
||||||
|
|
@ -275,6 +375,7 @@ The principal source-only gates are:
|
||||||
~~~sh
|
~~~sh
|
||||||
make WITH_SDK=0 WITH_PQBOX=0 check
|
make WITH_SDK=0 WITH_PQBOX=0 check
|
||||||
make WITH_SDK=0 WITH_PQBOX=0 test-all
|
make WITH_SDK=0 WITH_PQBOX=0 test-all
|
||||||
|
make sdk-test
|
||||||
make test-asan
|
make test-asan
|
||||||
make test-asan-run
|
make test-asan-run
|
||||||
make audit-licenses
|
make audit-licenses
|
||||||
|
|
@ -307,14 +408,34 @@ sanitizer-detected crash. An earlier off-screen GUI smoke run remains supporting
|
||||||
evidence rather than an exact-candidate package result.
|
evidence rather than an exact-candidate package result.
|
||||||
|
|
||||||
Those results are historical upstream self-audit evidence, not independent
|
Those results are historical upstream self-audit evidence, not independent
|
||||||
certification and not 5.2.3 results. Post-tag CI integration failures prevented
|
certification and not 5.2.8 results. Post-tag CI integration failures prevented
|
||||||
5.2.2 promotion. The exact 5.2.3 candidate must repeat all required gates;
|
5.2.2 promotion. The immutable 5.2.3 candidate was also not promoted because its
|
||||||
native Windows and macOS, hosted GitHub CI/release promotion, authenticated OBS,
|
source-policy test assumed LF for a `.bat` checkout that correctly used CRLF.
|
||||||
and resolution of the openSUSE automatic `debugsource` rpmlint `no-binary`
|
The immutable v5.2.4 candidate then recorded 12 successful jobs in exact-tag CI
|
||||||
finding remain pending until recorded otherwise. Unexecuted gates are `SKIP`,
|
run `33431386002`; the sole openSUSE service-harness job failed because the
|
||||||
never `PASS`.
|
standalone executor did not enter its service directory, so dependent Windows
|
||||||
|
and macOS jobs were skipped. A local Tumbleweed reproduction proved the explicit
|
||||||
|
tag ref and corrected working-directory contract, but neither that reproduction
|
||||||
|
nor the successful v5.2.4 jobs are v5.2.8 evidence. The immutable v5.2.5
|
||||||
|
candidate was not promoted after exact-tag GitHub Actions run `33434986357`:
|
||||||
|
13 jobs succeeded, but the native Windows hostile-path fixture and macOS
|
||||||
|
build/check gate failed. Their 5.2.6 corrections were followed by exact-tag run
|
||||||
|
`33442264243`, which also completed 13 jobs successfully but failed native
|
||||||
|
macOS on arm64-unused SHA-NI helper declarations under `-Werror` and native
|
||||||
|
Windows during safe UTF-8 fixture argv transcoding. The immutable v5.2.6 tag was
|
||||||
|
not promoted. The immutable v5.2.7 tag was also not promoted: exact-tag run
|
||||||
|
`33445470664` reached the macOS raw-C1 filename-creation failure with `EILSEQ`,
|
||||||
|
recorded 13 successful jobs, and cancelled Windows after the hosted job stalled
|
||||||
|
in `make check`; a MinGW/Wine reproduction isolated the stall to
|
||||||
|
`test --password-prompt ... </dev/null` entering `_getch`. The
|
||||||
|
exact 5.2.8 candidate then repeated all required gates: exact-tag run
|
||||||
|
`33456209269` completed 15/15 jobs successfully, including native Windows and
|
||||||
|
macOS, the pinned local OBS service chain, source/package gates, and the
|
||||||
|
openSUSE RPM checks. Promotion run `33457868306` published the exact tested
|
||||||
|
asset allowlist. Unexecuted environments remain `SKIP`, never `PASS`; these
|
||||||
|
project-run results are not independent certification.
|
||||||
|
|
||||||
On Windows, 5.2.3 scopes output handling to normal local Win32 paths. A MinGW
|
On Windows, 5.2.8 scopes output handling to normal local Win32 paths. A MinGW
|
||||||
cross-build or Wine run is not native-Windows evidence; the `windows-latest`
|
cross-build or Wine run is not native-Windows evidence; the `windows-latest`
|
||||||
package job, including its Unicode round trip, remains a mandatory publication
|
package job, including its Unicode round trip, remains a mandatory publication
|
||||||
gate. Win32 extended-length and device-namespace paths, raw UNC output roots
|
gate. Win32 extended-length and device-namespace paths, raw UNC output roots
|
||||||
|
|
@ -336,7 +457,7 @@ downgrading authentication of header and footer metadata.
|
||||||
`disk restore`, and exists only to recover a known, trusted archive created
|
`disk restore`, and exists only to recover a known, trusted archive created
|
||||||
before AIT was introduced. Do not use that override for an archive from
|
before AIT was introduced. Do not use that override for an archive from
|
||||||
untrusted or attacker-writable storage; verify and migrate the recovered data to
|
untrusted or attacker-writable storage; verify and migrate the recovered data to
|
||||||
a newly created 5.2.3 archive. Compression and disk backup never create a
|
a newly created 5.2.8 archive. Compression and disk backup never create a
|
||||||
no-AIT archive.
|
no-AIT archive.
|
||||||
|
|
||||||
`info` is deliberately different: it reports unauthenticated framing metadata,
|
`info` is deliberately different: it reports unauthenticated framing metadata,
|
||||||
|
|
@ -354,10 +475,9 @@ lists, tests, extracts, and restores it byte-exact. The full local Linux gate
|
||||||
passed on commit `ff99770`. This is not a claim that a 5.2.1 reader understands every new
|
passed on commit `ff99770`. This is not a claim that a 5.2.1 reader understands every new
|
||||||
flag-gated 5.2.2 encoding or that every historical combination was tested.
|
flag-gated 5.2.2 encoding or that every historical combination was tested.
|
||||||
|
|
||||||
The candidate commands and outcome fields for 5.2.3 are maintained in the
|
The build/audit commands and recorded 5.2.8 outcomes are maintained in the
|
||||||
release handoff and
|
release evidence and
|
||||||
[packaging/opensuse/README.md](packaging/opensuse/README.md). They must be
|
[packaging/opensuse/README.md](packaging/opensuse/README.md). No architecture or
|
||||||
updated from the final release candidate before tagging. No architecture or
|
|
||||||
distribution is claimed merely because the code has a fallback path.
|
distribution is claimed merely because the code has a fallback path.
|
||||||
|
|
||||||
## Source archive
|
## Source archive
|
||||||
|
|
@ -366,9 +486,9 @@ Generate the reproducible source archive outside the repository:
|
||||||
|
|
||||||
~~~sh
|
~~~sh
|
||||||
make dist
|
make dist
|
||||||
sha256sum /tmp/zupt-5.2.3.tar.gz
|
sha256sum /tmp/zupt-5.2.8.tar.gz
|
||||||
bash scripts/check-source-only.sh \
|
bash scripts/check-source-only.sh \
|
||||||
--archive /tmp/zupt-5.2.3.tar.gz
|
--archive /tmp/zupt-5.2.8.tar.gz
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
Archive ordering, ownership and timestamps are normalized. The default epoch is
|
Archive ordering, ownership and timestamps are normalized. The default epoch is
|
||||||
|
|
@ -378,13 +498,13 @@ the same SHA-256. The AUR, Homebrew and Guix recipes are `export-ignore` so
|
||||||
their checksum fields do not make the archive self-referential. `make dist`
|
their checksum fields do not make the archive self-referential. `make dist`
|
||||||
archives the verified `HEAD` tree object rather than embedding the commit ID,
|
archives the verified `HEAD` tree object rather than embedding the commit ID,
|
||||||
so a commit changing only those ignored recipes leaves the fixed-epoch archive
|
so a commit changing only those ignored recipes leaves the fixed-epoch archive
|
||||||
byte-identical. The recipes remain versioned in Git and must be filled with the
|
byte-identical. The recipes remain versioned in Git and pin the final tagged
|
||||||
final digest before the tag is published.
|
digest `378b9506211545b9594cf0d38ac8955d9b1cac34eb6b379ae0ec26b84edb65f7`.
|
||||||
|
|
||||||
## openSUSE and OBS
|
## openSUSE and OBS
|
||||||
|
|
||||||
The maintained upstream recipe is in packaging/opensuse. It is prepared for an
|
The maintained upstream recipe is in packaging/opensuse. It targets the
|
||||||
immutable v5.2.3 tag, disables submodules and Git LFS, builds with
|
immutable v5.2.8 tag, disables submodules and Git LFS, builds with
|
||||||
WITH_SDK=0 WITH_PQBOX=0, runs real checks, and installs without the renamed-era
|
WITH_SDK=0 WITH_PQBOX=0, runs real checks, and installs without the renamed-era
|
||||||
`vaptvupt` alias.
|
`vaptvupt` alias.
|
||||||
|
|
||||||
|
|
@ -427,7 +547,7 @@ The optional GUI is under `gui/`. It invokes the `zupt` CLI and needs Python 3
|
||||||
plus PySide6 or PyQt6. GUI image assets are data files whose purpose,
|
plus PySide6 or PyQt6. GUI image assets are data files whose purpose,
|
||||||
provenance and license are recorded in [gui/assets/README.md](gui/assets/README.md).
|
provenance and license are recorded in [gui/assets/README.md](gui/assets/README.md).
|
||||||
The integrated source and lightweight consistency checks do not constitute a
|
The integrated source and lightweight consistency checks do not constitute a
|
||||||
target-native audit of every historical GUI format. The 5.2.3 artifact promise
|
target-native audit of every historical GUI format. The 5.2.8 artifact promise
|
||||||
is limited to the gated GUI DEB, noarch/source RPM, and source-only portable ZIP
|
is limited to the gated GUI DEB, noarch/source RPM, and source-only portable ZIP
|
||||||
listed above; AppImage, AppDir, Flatpak bundles, and platform GUI installers
|
listed above; AppImage, AppDir, Flatpak bundles, and platform GUI installers
|
||||||
remain excluded.
|
remain excluded.
|
||||||
|
|
@ -436,13 +556,15 @@ remain excluded.
|
||||||
|
|
||||||
Cristian Cezar Moisés is the creator and current upstream maintainer of ZUPT and
|
Cristian Cezar Moisés is the creator and current upstream maintainer of ZUPT and
|
||||||
the author of the current upstream source, build, test, documentation, and
|
the author of the current upstream source, build, test, documentation, and
|
||||||
packaging changes, including the 5.2.2 baseline and corrective 5.2.3 work.
|
packaging changes, including the 5.2.2 baseline and corrective
|
||||||
|
5.2.3/5.2.4/5.2.5/5.2.6/5.2.7/5.2.8 work.
|
||||||
|
|
||||||
Alessandro de Oliveira Faria (Cabelo) is credited as the openSUSE collaborator
|
Alessandro de Oliveira Faria (Cabelo) is credited as the openSUSE collaborator
|
||||||
and downstream package maintainer. He reviews the handoff, commits it in the
|
and downstream package maintainer. He reviews the handoff, commits it in the
|
||||||
OBS project he maintains, and may make the additional openSUSE-side adjustments
|
OBS project he maintains, and may make the additional openSUSE-side adjustments
|
||||||
he considers necessary. That downstream role is not attribution of ZUPT source
|
he considers necessary. That downstream role is not attribution of ZUPT source
|
||||||
authorship or of the upstream 5.2.2 or 5.2.3 changes.
|
authorship or of the upstream 5.2.2, 5.2.3, 5.2.4, 5.2.5, 5.2.6, 5.2.7, or
|
||||||
|
5.2.8 changes.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
|
||||||
123
SECURITY.md
123
SECURITY.md
|
|
@ -1,4 +1,4 @@
|
||||||
# Security Policy — ZUPT 5.2.3
|
# Security Policy — ZUPT 5.2.8
|
||||||
|
|
||||||
## Reporting vulnerabilities
|
## Reporting vulnerabilities
|
||||||
|
|
||||||
|
|
@ -49,6 +49,9 @@ On POSIX terminals, the explicit prompt saves terminal state and installs
|
||||||
signal-aware cleanup so a handled interruption restores echo and other changed
|
signal-aware cleanup so a handled interruption restores echo and other changed
|
||||||
settings before termination. This behavior is covered by a PTY regression and
|
settings before termination. This behavior is covered by a PTY regression and
|
||||||
passed in the full local Linux gate for commit `ff99770`.
|
passed in the full local Linux gate for commit `ff99770`.
|
||||||
|
On Windows, the prompt requires a real console input handle before entering
|
||||||
|
`_getch`; redirected input and console EOF fail instead of blocking a native
|
||||||
|
release gate.
|
||||||
|
|
||||||
## Native key files
|
## Native key files
|
||||||
|
|
||||||
|
|
@ -66,7 +69,7 @@ partially accepted.
|
||||||
|
|
||||||
### Optional integrations
|
### Optional integrations
|
||||||
|
|
||||||
The 5.2.3 default is `WITH_SDK=0 WITH_PQBOX=0`:
|
The 5.2.8 default is `WITH_SDK=0 WITH_PQBOX=0`:
|
||||||
|
|
||||||
- `WITH_SDK=1` enables libvuptsdk-backed features, including the SDK PQ mode
|
- `WITH_SDK=1` enables libvuptsdk-backed features, including the SDK PQ mode
|
||||||
and Argon2id support, using a separately installed system development package.
|
and Argon2id support, using a separately installed system development package.
|
||||||
|
|
@ -78,6 +81,13 @@ downloads it. A missing requested dependency is a build error. Security
|
||||||
properties of these optional libraries are outside the source-only CLI audit
|
properties of these optional libraries are outside the source-only CLI audit
|
||||||
unless their exact source package and version are reviewed separately.
|
unless their exact source package and version are reviewed separately.
|
||||||
|
|
||||||
|
The in-repository SDK adapter saves copied keys through the core atomic output
|
||||||
|
publisher. POSIX permissions are applied to its already-open temporary
|
||||||
|
descriptor, and publication replaces only the requested directory entry after
|
||||||
|
copy/close checks succeed. `make sdk-test` exercises private/public modes and
|
||||||
|
preservation of pre-existing symlink and hardlink targets. This covers the
|
||||||
|
adapter boundary; it does not certify the separately installed SDK library.
|
||||||
|
|
||||||
## Cryptographic construction
|
## Cryptographic construction
|
||||||
|
|
||||||
Encrypted blocks use a fresh 128-bit nonce, AES-256-CTR, and HMAC-SHA256. The
|
Encrypted blocks use a fresh 128-bit nonce, AES-256-CTR, and HMAC-SHA256. The
|
||||||
|
|
@ -133,12 +143,19 @@ can compromise archives encrypted to it.
|
||||||
|
|
||||||
## Constant-time and side-channel scope
|
## Constant-time and side-channel scope
|
||||||
|
|
||||||
Portable C is the 5.2.3 default. Sensitive comparisons and selections use
|
Portable C is the 5.2.8 default. Sensitive comparisons and selections use
|
||||||
branchless helpers, but generated machine-code behavior remains dependent on
|
branchless helpers, but generated machine-code behavior remains dependent on
|
||||||
the compiler and platform. This is not a formal whole-program constant-time
|
the compiler and platform. This is not a formal whole-program constant-time
|
||||||
claim. The C AES implementation uses table lookups and is unsuitable for a
|
claim. The C AES implementation uses table lookups and is unsuitable for a
|
||||||
claim of cache-timing resistance on hostile shared hardware.
|
claim of cache-timing resistance on hostile shared hardware.
|
||||||
|
|
||||||
|
Sensitive VaptVupt working buffers are cleared through a compiler-resistant
|
||||||
|
wipe helper. Platforms with a guaranteed libc `explicit_bzero` use it; macOS
|
||||||
|
and NetBSD use the portable volatile-write fallback because the supported
|
||||||
|
deployment targets do not guarantee that symbol. This source-level choice
|
||||||
|
resists ordinary dead-store elimination but is not a formal claim about every
|
||||||
|
compiler binary.
|
||||||
|
|
||||||
Textual assembly under `jasmin/` can be enabled explicitly with
|
Textual assembly under `jasmin/` can be enabled explicitly with
|
||||||
`WITH_JASMIN=1` on a supported x86_64 compiler target. The directory contains
|
`WITH_JASMIN=1` on a supported x86_64 compiler target. The directory contains
|
||||||
Jasmin-generated output and separately identified hand-written assembly; all of
|
Jasmin-generated output and separately identified hand-written assembly; all of
|
||||||
|
|
@ -183,21 +200,50 @@ appropriate. Extract untrusted archives as a dedicated unprivileged user into
|
||||||
a new empty directory, inspect the result before moving it, and apply OS
|
a new empty directory, inspect the result before moving it, and apply OS
|
||||||
sandboxing where available.
|
sandboxing where available.
|
||||||
|
|
||||||
|
Benchmark workspaces are random private directories. POSIX cleanup opens each
|
||||||
|
directory component without following links and removes entries relative to
|
||||||
|
pinned descriptors. Windows retains no-delete-sharing handles for the resolved
|
||||||
|
ancestors and refuses reparse-point recursion. After emptying a directory, it
|
||||||
|
reopens that entry relative to the pinned parent, verifies the volume and file
|
||||||
|
index against the traversal handle, and marks only the identity-checked handle
|
||||||
|
for deletion. An injected link is removed as a link rather than traversed to
|
||||||
|
its target.
|
||||||
|
|
||||||
Disk restore has a separate destructive-device boundary. It measures and
|
Disk restore has a separate destructive-device boundary. It measures and
|
||||||
copies the compacted archive to an exclusively created, auto-deleted private
|
copies the compacted archive to an exclusively created, auto-deleted private
|
||||||
scratch file before opening the target, then performs both validation and
|
scratch file, then performs validation and restoration from that same snapshot.
|
||||||
restoration from that same snapshot. `ZUPT_TMPDIR` is an explicit existing
|
`ZUPT_TMPDIR` is an explicit existing scratch-directory override; an invalid
|
||||||
scratch-directory override; an invalid override fails without fallback. Raw
|
override fails without fallback. On POSIX, the target is opened once without
|
||||||
block devices are opened only after their capacity has been determined and
|
truncation or final-symlink following, classified with `fstat`, and—when it is
|
||||||
shown sufficient on supported Linux, macOS, or FreeBSD interfaces. Unknown
|
a supported Linux, macOS, or FreeBSD raw block device—the same descriptor is
|
||||||
device capacity, an undersized device, a source/destination identity match, or
|
retained through capacity checks and writes. Regular files continue through
|
||||||
a snapshot failure stops before the first target write. These checks do not
|
atomic publication. Unknown device capacity, an undersized device, a
|
||||||
|
source/destination identity match, or a snapshot failure stops before the
|
||||||
|
first target write. These checks do not
|
||||||
make raw-device restore non-destructive: verify both operands and keep recovery
|
make raw-device restore non-destructive: verify both operands and keep recovery
|
||||||
media before proceeding.
|
media before proceeding.
|
||||||
|
|
||||||
|
These changes address the three 5.2.8 CodeQL High reports: #5 at SDK key
|
||||||
|
publication, #6 at POSIX disk-target classification/use, and #7 at benchmark
|
||||||
|
workspace cleanup. The regressions and source review are project evidence, not
|
||||||
|
an independent certification. Exact-tag run `33456209269` subsequently passed
|
||||||
|
all 15 jobs at `ebb9ab3aa1d42c50030ca02883f6162dc4771fe1`.
|
||||||
|
|
||||||
|
The C/C++ default-branch analysis of commit `69fc26b` closed #5, #6, and #7,
|
||||||
|
then opened test-only High #8, #9, and #10 because the new SDK regression used
|
||||||
|
path-level `stat`/`lstat` before later reads or cleanup. Each individual test
|
||||||
|
check now uses a no-follow descriptor with `fstat` or descriptor reads; the
|
||||||
|
static gate rejects reintroduction of path-level metadata checks there. The
|
||||||
|
subsequent C/C++ default-branch scan run `33452563116` completed successfully at
|
||||||
|
commit `7a8e5c5`; alerts #5 through #10 are fixed, and the authenticated
|
||||||
|
code-scanning API reported zero open alerts.
|
||||||
|
The final release-commit CodeQL run `33456049125` also completed successfully;
|
||||||
|
the authenticated API again reported zero open alerts, with #5 through #10
|
||||||
|
recorded as fixed rather than dismissed.
|
||||||
|
|
||||||
The Windows handle-relative implementation is scoped to normal local Win32
|
The Windows handle-relative implementation is scoped to normal local Win32
|
||||||
paths. Win32 extended-length and device-namespace paths, raw UNC output roots,
|
paths. Win32 extended-length and device-namespace paths, raw UNC output roots,
|
||||||
and mapped/network-drive output are not supported in 5.2.3. Cross-build and
|
and mapped/network-drive output are not supported in 5.2.8. Cross-build and
|
||||||
Wine results are not native-Windows evidence; the `windows-latest` package gate
|
Wine results are not native-Windows evidence; the `windows-latest` package gate
|
||||||
must pass its Unicode round trip before Windows assets are published. Restore
|
must pass its Unicode round trip before Windows assets are published. Restore
|
||||||
to a normal local directory first and move verified output to network storage
|
to a normal local directory first and move verified output to network storage
|
||||||
|
|
@ -240,7 +286,7 @@ shared/static library, or distribution package. Audit them with:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
scripts/check-source-only.sh
|
scripts/check-source-only.sh
|
||||||
scripts/check-source-only.sh --archive /path/to/zupt-5.2.3.tar.gz
|
scripts/check-source-only.sh --archive /path/to/zupt-5.2.8.tar.gz
|
||||||
```
|
```
|
||||||
|
|
||||||
Nested archive inspection is required to enforce bounded recursion, member
|
Nested archive inspection is required to enforce bounded recursion, member
|
||||||
|
|
@ -250,13 +296,13 @@ limit violations. On commit `ff99770`, the source-only scanner suite passed
|
||||||
|
|
||||||
DEB, binary RPM, SRPM, notice-bearing Linux tar.xz, source-only portable GUI
|
DEB, binary RPM, SRPM, notice-bearing Linux tar.xz, source-only portable GUI
|
||||||
ZIP, Windows ZIP, and macOS DMG release assets are separate outputs. An
|
ZIP, Windows ZIP, and macOS DMG release assets are separate outputs. An
|
||||||
AppImage is not promoted for 5.2.3. A bare Linux or Windows executable is also
|
AppImage is not promoted for 5.2.8. A bare Linux or Windows executable is also
|
||||||
excluded; executables are distributed only inside their notice-bearing
|
excluded; executables are distributed only inside their notice-bearing
|
||||||
archives. Trust an artifact only when its exact format has a recorded build,
|
archives. Trust an artifact only when its exact format has a recorded build,
|
||||||
content/metadata inspection, extracted or installed smoke test, and applicable
|
content/metadata inspection, extracted or installed smoke test, and applicable
|
||||||
archive round trip. Never treat an unexecuted platform as passing.
|
archive round trip. Never treat an unexecuted platform as passing.
|
||||||
|
|
||||||
The gated 5.2.3 set is the CLI package/archive set plus the exact GUI DEB,
|
The gated 5.2.8 set is the CLI package/archive set plus the exact GUI DEB,
|
||||||
noarch/source RPM, and source-only portable ZIP documented in the README. The
|
noarch/source RPM, and source-only portable ZIP documented in the README. The
|
||||||
portable GUI ZIP contains no compiled runtime and is scanned as source before
|
portable GUI ZIP contains no compiled runtime and is scanned as source before
|
||||||
and after extraction. Other GUI packages, AppImage, AppDir and Flatpak bundles,
|
and after extraction. Other GUI packages, AppImage, AppDir and Flatpak bundles,
|
||||||
|
|
@ -273,6 +319,7 @@ make -j"$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf 1)" \
|
||||||
WITH_SDK=0 WITH_PQBOX=0 V=1
|
WITH_SDK=0 WITH_PQBOX=0 V=1
|
||||||
make WITH_SDK=0 WITH_PQBOX=0 check
|
make WITH_SDK=0 WITH_PQBOX=0 check
|
||||||
make WITH_SDK=0 WITH_PQBOX=0 test-all
|
make WITH_SDK=0 WITH_PQBOX=0 test-all
|
||||||
|
make sdk-test
|
||||||
```
|
```
|
||||||
|
|
||||||
Where the compiler supports them, run the sanitizer target separately:
|
Where the compiler supports them, run the sanitizer target separately:
|
||||||
|
|
@ -296,11 +343,49 @@ result.
|
||||||
|
|
||||||
Post-tag CI integration failures prevented 5.2.2 promotion. Those upstream
|
Post-tag CI integration failures prevented 5.2.2 promotion. Those upstream
|
||||||
self-audit results are not independent certification and do not transfer to
|
self-audit results are not independent certification and do not transfer to
|
||||||
5.2.3. The exact 5.2.3 candidate must repeat the required suite. Native Windows
|
5.2.8. The immutable 5.2.3 candidate was not promoted because its source-policy
|
||||||
and macOS, hosted GitHub CI/release promotion, authenticated OBS, and the
|
test assumed LF for a Windows `.bat` file checked out as CRLF. The immutable
|
||||||
openSUSE automatic `debugsource` rpmlint `no-binary` finding remain pending
|
v5.2.4 candidate was not promoted after exact-tag GitHub Actions run
|
||||||
until recorded otherwise. An unavailable or unexecuted environment remains
|
`33431386002`: 12 jobs succeeded, the sole openSUSE job failed in its
|
||||||
`SKIP`, never `PASS`.
|
standalone source-service harness because it did not enter the service
|
||||||
|
directory, and dependent Windows and macOS jobs were skipped. A local
|
||||||
|
Tumbleweed reproduction confirmed that `refs/tags/v5.2.4` is valid and that
|
||||||
|
`os.chdir(service_dir)` lets `obs_scm`, `tar`, and `recompress` complete with a
|
||||||
|
source-scanned archive. This was a release/test integration defect, not a
|
||||||
|
product, archive, cryptographic, codec, or SDK ABI change, and its evidence does
|
||||||
|
not transfer automatically to 5.2.8. The immutable v5.2.5 candidate was also
|
||||||
|
not promoted: exact-tag GitHub Actions run `33434986357` recorded 13 successful
|
||||||
|
jobs and failed native Windows/macOS jobs. Its Windows fixture-byte and macOS
|
||||||
|
secure-wipe/Bash 3.2 defects were corrected for 5.2.6. A targeted clean-clone
|
||||||
|
run of the corrected scanner under genuine GNU Bash 3.2.57 passed repository, standalone
|
||||||
|
tree, standalone archive, and root-plus-tag modes; that local compatibility
|
||||||
|
result does not transfer to any other gate. Exact-tag v5.2.6 run `33442264243`
|
||||||
|
then completed 13 jobs successfully but failed native macOS because x86 SHA-NI
|
||||||
|
test helpers were unused on arm64 under `-Werror`, and failed native Windows
|
||||||
|
when argv transcoding aborted the safe UTF-8 fixture. Those are test-harness
|
||||||
|
integration defects, not product, archive, cryptographic, codec, or SDK ABI
|
||||||
|
changes; v5.2.6 remained unpromoted, so its results did not transfer to the
|
||||||
|
required 5.2.8 suite. The immutable v5.2.7 candidate was likewise not
|
||||||
|
promoted: exact-tag run `33445470664` concluded `cancelled` at
|
||||||
|
`2026-08-31T23:11:19Z`, with 13 successful jobs, one failed macOS job after
|
||||||
|
raw-C1 fixture creation returned `EILSEQ`, and one cancelled Windows job after
|
||||||
|
the hosted job stalled in `make check`; a MinGW/Wine reproduction isolated the
|
||||||
|
cause to a redirected password prompt entering `_getch`. Version 5.2.8 makes
|
||||||
|
both test boundaries fail or skip without hanging. Manual pre-tag run
|
||||||
|
`33452602634` subsequently passed 14 of 15 jobs, including the native macOS
|
||||||
|
DMG and the Windows source audit, build, and full distribution checks. The
|
||||||
|
remaining Windows smoke failure was an old MSYS `grep` non-BMP pattern boundary
|
||||||
|
after ZUPT had compressed and verified all inputs; MinGW/Wine confirmed ZUPT's
|
||||||
|
byte-exact UTF-8 listing. The corrected gate validates Latin-1, BMP, and
|
||||||
|
non-BMP listing bytes without locale-sensitive matching, then requires
|
||||||
|
extraction and a full tree diff. The failed run is not exact-candidate
|
||||||
|
evidence. Exact-tag run `33456209269` then completed 15/15 jobs successfully,
|
||||||
|
including native Windows/macOS, the pinned local OBS service chain, package
|
||||||
|
installation/round trips, source-only checks, analyzers, and sanitizers.
|
||||||
|
Promotion run `33457868306` published the exact 13-file allowlist after
|
||||||
|
format, metadata, payload, and checksum validation. An unavailable or
|
||||||
|
unexecuted environment remains `SKIP`, never `PASS`; successful project CI is
|
||||||
|
still not independent security certification.
|
||||||
|
|
||||||
Run target-native static analyzers and package checks as additional evidence.
|
Run target-native static analyzers and package checks as additional evidence.
|
||||||
Do not infer x86_64, aarch64, ppc64le, s390x, riscv64, macOS, Windows, Leap, or
|
Do not infer x86_64, aarch64, ppc64le, s390x, riscv64, macOS, Windows, Leap, or
|
||||||
|
|
|
||||||
|
|
@ -159,7 +159,7 @@ grant attached to their unchanged Git blobs, are recorded in
|
||||||
|
|
||||||
## AppImage type-2 runtime
|
## AppImage type-2 runtime
|
||||||
|
|
||||||
No AppImage is a promised or promoted 5.2.3 release asset. The upstream
|
No AppImage is a promised or promoted 5.2.8 release asset. The upstream
|
||||||
type-2 runtime inspected during the 5.2.2 review statically linked musl, libfuse,
|
type-2 runtime inspected during the 5.2.2 review statically linked musl, libfuse,
|
||||||
squashfuse, zstd, zlib, and mimalloc, but its own license notice did not list
|
squashfuse, zstd, zlib, and mimalloc, but its own license notice did not list
|
||||||
mimalloc and the available release inputs did not provide a complete
|
mimalloc and the available release inputs did not provide a complete
|
||||||
|
|
@ -171,7 +171,7 @@ no network input and requires the operator to supply both a locally verified
|
||||||
runtime and `APPIMAGE_RUNTIME_COMPLIANCE_FILE`, containing the license notices,
|
runtime and `APPIMAGE_RUNTIME_COMPLIANCE_FILE`, containing the license notices,
|
||||||
source correspondence or offer, and relink information applicable to those
|
source correspondence or offer, and relink information applicable to those
|
||||||
exact runtime bytes. An artifact produced independently with that helper is
|
exact runtime bytes. An artifact produced independently with that helper is
|
||||||
not covered by the 5.2.3 upstream release gates.
|
not covered by the 5.2.8 upstream release gates.
|
||||||
|
|
||||||
## Reporting attribution issues
|
## Reporting attribution issues
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# ZUPT 5.2.3 threat model
|
# ZUPT 5.2.8 threat model
|
||||||
|
|
||||||
This document defines the security boundary of the ZUPT archive tool. It is
|
This document defines the security boundary of the ZUPT archive tool. It is
|
||||||
not a certification, a guarantee against every hostile input, or a substitute
|
not a certification, a guarantee against every hostile input, or a substitute
|
||||||
|
|
@ -17,7 +17,7 @@ plausibly deniable.
|
||||||
|
|
||||||
## Baseline considered here
|
## Baseline considered here
|
||||||
|
|
||||||
The upstream baseline is built from the 5.2.3 source with:
|
The upstream baseline is built from the 5.2.8 source with:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
make WITH_SDK=0 WITH_PQBOX=0
|
make WITH_SDK=0 WITH_PQBOX=0
|
||||||
|
|
@ -88,6 +88,8 @@ duplicates but shares the underlying stream/offset and may buffer beyond the
|
||||||
line, so callers should provide a descriptor dedicated to that password read.
|
line, so callers should provide a descriptor dedicated to that password read.
|
||||||
On POSIX, handled prompt interruptions restore the saved terminal state before
|
On POSIX, handled prompt interruptions restore the saved terminal state before
|
||||||
termination; an exact-candidate PTY regression is required before release.
|
termination; an exact-candidate PTY regression is required before release.
|
||||||
|
On Windows, a prompt is entered only for a real console input handle;
|
||||||
|
redirected input and console EOF fail instead of blocking in `_getch`.
|
||||||
|
|
||||||
Native private-key generation uses no-replace creation with POSIX mode `0600`
|
Native private-key generation uses no-replace creation with POSIX mode `0600`
|
||||||
or a Windows current-user-only DACL. A failed write, flush/fsync, or close leaves
|
or a Windows current-user-only DACL. A failed write, flush/fsync, or close leaves
|
||||||
|
|
@ -99,6 +101,13 @@ and public/private role validation. This prevents role confusion and
|
||||||
partial/trailing-key acceptance; it does not protect a key after endpoint or
|
partial/trailing-key acceptance; it does not protect a key after endpoint or
|
||||||
account compromise.
|
account compromise.
|
||||||
|
|
||||||
|
When the optional system SDK is enabled, the in-repository adapter copies a key
|
||||||
|
through the core atomic publisher, applies POSIX mode through the already-open
|
||||||
|
temporary descriptor, and publishes only after copy/close checks succeed. Its
|
||||||
|
`sdk-test` regression preserves existing symlink/hardlink targets and verifies
|
||||||
|
private/public modes. This narrows the adapter boundary; it does not extend the
|
||||||
|
baseline assessment to the external SDK implementation.
|
||||||
|
|
||||||
### Encrypted archive integrity
|
### Encrypted archive integrity
|
||||||
|
|
||||||
Current encrypted archives authenticate ciphertext, canonical block metadata,
|
Current encrypted archives authenticate ciphertext, canonical block metadata,
|
||||||
|
|
@ -175,22 +184,39 @@ temporary through its descriptor or handle. These controls reduce traversal,
|
||||||
link, race, and partial-output risks, but do not establish that no parser or
|
link, race, and partial-output risks, but do not establish that no parser or
|
||||||
filesystem bug can exist.
|
filesystem bug can exist.
|
||||||
|
|
||||||
The Windows handle-relative boundary in 5.2.3 covers normal local Win32 paths.
|
Benchmark scratch data lives in a random private directory. Cleanup resolves
|
||||||
|
POSIX components without following links and deletes relative to pinned
|
||||||
|
descriptors. On Windows it retains no-delete-sharing ancestor handles, refuses
|
||||||
|
reparse-point recursion, then reopens each emptied directory relative to its
|
||||||
|
pinned parent and verifies its filesystem identity before handle-based
|
||||||
|
deletion. An attacker who inserts a link can cause cleanup failure, but the
|
||||||
|
cleanup must not traverse to the link target.
|
||||||
|
|
||||||
|
The Windows handle-relative boundary in 5.2.8 covers normal local Win32 paths.
|
||||||
Win32 extended-length and device-namespace paths, raw UNC output roots, and
|
Win32 extended-length and device-namespace paths, raw UNC output roots, and
|
||||||
mapped/network-drive output are not supported. Cross-build and Wine results are
|
mapped/network-drive output are not supported. Cross-build and Wine results are
|
||||||
not a substitute for the required native `windows-latest` Unicode package
|
not a substitute for the required native `windows-latest` Unicode package
|
||||||
gate. Restore locally before moving verified output to network storage.
|
gate. Restore locally before moving verified output to network storage.
|
||||||
|
|
||||||
Disk restore copies the measured compacted archive into one exclusively
|
Disk restore copies the measured compacted archive into one exclusively
|
||||||
created, auto-deleted scratch file before it opens a destructive destination.
|
created, auto-deleted scratch file. Preflight and restoration consume that same
|
||||||
Preflight and restoration consume that same open snapshot. An explicit
|
open snapshot. An explicit `ZUPT_TMPDIR` selects an existing scratch directory;
|
||||||
`ZUPT_TMPDIR` selects an existing scratch directory; failure there does not
|
failure there does not fall back to consuming the mutable source pathname. On
|
||||||
fall back to consuming the mutable source pathname. On supported Linux, macOS,
|
POSIX, the destination is opened once without truncation or final-symlink
|
||||||
and FreeBSD interfaces, a raw block-device target is rejected before writing if
|
following, classified with `fstat`, and the same raw-device descriptor is
|
||||||
its capacity is unknown or smaller than the image. These controls reduce source
|
retained for supported Linux, macOS, and FreeBSD capacity checks and writes.
|
||||||
exchange and immediate overrun risk but do not protect against a compromised
|
Regular-file output retains atomic publication. A raw target is rejected before
|
||||||
kernel/device, a wrongly selected sufficiently large device, power loss, or
|
writing if its capacity is unknown or smaller than the image. These controls
|
||||||
hardware failure.
|
reduce source exchange, target exchange, and immediate overrun risk but do not
|
||||||
|
protect against a compromised kernel/device, a wrongly selected sufficiently
|
||||||
|
large device, power loss, or hardware failure.
|
||||||
|
|
||||||
|
The SDK publication, POSIX disk-target, and benchmark-cleanup changes address
|
||||||
|
CodeQL High #5, #6, and #7 respectively. Their source review and regressions
|
||||||
|
alone are project evidence, not independent certification. Exact-tag run
|
||||||
|
`33456209269` subsequently passed all 15 hosted jobs at
|
||||||
|
`ebb9ab3aa1d42c50030ca02883f6162dc4771fe1`; final release-commit CodeQL run
|
||||||
|
`33456049125` completed successfully with zero open alerts.
|
||||||
|
|
||||||
For an untrusted archive:
|
For an untrusted archive:
|
||||||
|
|
||||||
|
|
@ -250,9 +276,9 @@ tagged source. Each artifact extends the trust boundary to its builder,
|
||||||
toolchain, runner image, and packaging scripts. Treat it as validated only when
|
toolchain, runner image, and packaging scripts. Treat it as validated only when
|
||||||
the exact target has a recorded build, content/package inspection, extracted or
|
the exact target has a recorded build, content/package inspection, extracted or
|
||||||
installed smoke test, and applicable archive round trip. An AppImage is not
|
installed smoke test, and applicable archive round trip. An AppImage is not
|
||||||
promoted for 5.2.3; bare Linux and Windows executables are also excluded.
|
promoted for 5.2.8; bare Linux and Windows executables are also excluded.
|
||||||
|
|
||||||
For 5.2.3, that gated artifact scope covers the CLI files plus the exact GUI
|
For 5.2.8, that gated artifact scope covers the CLI files plus the exact GUI
|
||||||
DEB, noarch/source RPM, and source-only portable ZIP named in the README. The
|
DEB, noarch/source RPM, and source-only portable ZIP named in the README. The
|
||||||
portable ZIP contains no compiled runtime and crosses the release boundary only
|
portable ZIP contains no compiled runtime and crosses the release boundary only
|
||||||
after source scans and an exact safe-member check. AppDir and Flatpak bundles
|
after source scans and an exact safe-member check. AppDir and Flatpak bundles
|
||||||
|
|
@ -265,10 +291,37 @@ strict Clang, GCC `-fanalyzer`, the 9/9 tool-enabled static-analysis run,
|
||||||
ASan/UBSan/LSan, and 1,000 mutation-fuzz iterations passed. Earlier off-screen
|
ASan/UBSan/LSan, and 1,000 mutation-fuzz iterations passed. Earlier off-screen
|
||||||
GUI smoke evidence is retained separately. Post-tag CI integration failures
|
GUI smoke evidence is retained separately. Post-tag CI integration failures
|
||||||
prevented 5.2.2 promotion. This upstream self-review is not an independent
|
prevented 5.2.2 promotion. This upstream self-review is not an independent
|
||||||
certification and is not 5.2.3 evidence. Native Windows/macOS, hosted GitHub CI
|
certification and is not 5.2.8 evidence. The immutable 5.2.3 candidate was not
|
||||||
and release promotion, authenticated OBS, and the openSUSE automatic
|
promoted because its source-policy test assumed LF for a Windows `.bat` checkout
|
||||||
`debugsource` rpmlint `no-binary` finding remain pending until recorded
|
that correctly used CRLF. The immutable v5.2.4 candidate was not promoted after
|
||||||
otherwise.
|
exact-tag GitHub Actions run `33431386002`: 12 jobs succeeded, the sole openSUSE
|
||||||
|
service-harness job failed because its standalone executor did not enter the
|
||||||
|
service directory, and dependent Windows/macOS jobs were skipped. A local
|
||||||
|
Tumbleweed reproduction established that the explicit `refs/tags/v5.2.4`
|
||||||
|
revision works and that `os.chdir(service_dir)` completes the source-service
|
||||||
|
chain. This narrows the failure to release/test integration; it changes no
|
||||||
|
product, archive, cryptographic, codec, or SDK ABI boundary and supplies no
|
||||||
|
automatic 5.2.8 evidence. The immutable v5.2.5 candidate was not promoted after
|
||||||
|
exact-tag GitHub Actions run `33434986357`: 13 jobs succeeded, but native
|
||||||
|
Windows and macOS failed on fixture-byte preservation and Darwin/Bash 3.2
|
||||||
|
portability respectively. The corresponding 5.2.6 corrections were followed by
|
||||||
|
exact-tag run `33442264243`: 13 jobs succeeded, while native macOS failed on
|
||||||
|
x86-only SHA-NI helper declarations unused on arm64 under `-Werror`, and native
|
||||||
|
Windows aborted during safe UTF-8 fixture argv transcoding. The v5.2.6 tag was
|
||||||
|
not promoted. Version 5.2.7 corrected those two boundaries, but its exact-tag
|
||||||
|
run `33445470664` concluded `cancelled` at `2026-08-31T23:11:19Z`, with 13
|
||||||
|
successful jobs, one failed macOS job after raw-C1 filename creation returned
|
||||||
|
`EILSEQ`, and one cancelled Windows job after the hosted job stalled in `make
|
||||||
|
check`; a MinGW/Wine reproduction isolated the cause to a redirected password
|
||||||
|
prompt entering `_getch`.
|
||||||
|
The corresponding 5.2.8 fixture and prompt corrections alone did not establish
|
||||||
|
a result. Exact-tag run `33456209269` then passed 15/15 jobs, including
|
||||||
|
`sdk-test`, native Windows/macOS, the pinned local OBS source-service chain,
|
||||||
|
and the package/source gates. Promotion run `33457868306` published the exact
|
||||||
|
13 tested assets. Official authenticated OBS/Factory acceptance, the full
|
||||||
|
automatic debug-package result, and untested architectures remain unclaimed;
|
||||||
|
the earlier `debugsource` rpmlint `no-binary` finding remains unresolved and
|
||||||
|
unsuppressed.
|
||||||
|
|
||||||
## Historical compatibility notes
|
## Historical compatibility notes
|
||||||
|
|
||||||
|
|
@ -295,7 +348,7 @@ These are historical facts about earlier releases, retained to support recovery:
|
||||||
combinations remain unclaimed.
|
combinations remain unclaimed.
|
||||||
|
|
||||||
Historical test counts in the changelog describe those releases. They do not
|
Historical test counts in the changelog describe those releases. They do not
|
||||||
automatically become 5.2.3 results; current outcomes belong in the release
|
automatically become 5.2.8 results; current outcomes belong in the release
|
||||||
validation record, with unavailable environments marked `SKIP`. In particular,
|
validation record, with unavailable environments marked `SKIP`. In particular,
|
||||||
runs made before the final positional-AAD and mandatory-AIT changes are not
|
runs made before the final positional-AAD and mandatory-AIT changes are not
|
||||||
final release gates for the resulting candidate.
|
final release gates for the resulting candidate.
|
||||||
|
|
@ -306,4 +359,4 @@ Email **zupt@riseup.net** with `[security]` in the subject. Include the version,
|
||||||
platform, impact, and a minimal non-sensitive reproducer. Do not disclose the
|
platform, impact, and a minimal non-sensitive reproducer. Do not disclose the
|
||||||
issue publicly until a coordinated timeline has been agreed.
|
issue publicly until a coordinated timeline has been agreed.
|
||||||
|
|
||||||
Document version: 5.2.3, 2026-08-31.
|
Document version: 5.2.8, 2026-08-31.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
.\" SPDX-License-Identifier: AGPL-3.0-or-later
|
.\" SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
.\" Copyright (c) 2025-2026 Cristian Cezar Moisés
|
.\" Copyright (c) 2025-2026 Cristian Cezar Moisés
|
||||||
.TH ZUPT-GUI 1 "2026-08-31" "ZUPT 5.2.3" "User Commands"
|
.TH ZUPT-GUI 1 "2026-08-31" "ZUPT 5.2.8" "User Commands"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
zupt-gui \- Qt interface for the ZUPT backup utility
|
zupt-gui \- Qt interface for the ZUPT backup utility
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
|
|
@ -47,7 +47,7 @@ or
|
||||||
only when libvuptsdk or libpqvaptvupt is independently reported enabled.
|
only when libvuptsdk or libpqvaptvupt is independently reported enabled.
|
||||||
These two optional integrations are detected separately.
|
These two optional integrations are detected separately.
|
||||||
.PP
|
.PP
|
||||||
The gated 5.2.3 GUI release set is limited to the architecture-independent DEB,
|
The gated 5.2.8 GUI release set is limited to the architecture-independent DEB,
|
||||||
noarch/source RPM, and source-only portable ZIP named in the project README.
|
noarch/source RPM, and source-only portable ZIP named in the project README.
|
||||||
Package gates require exact checks and installed off-screen GUI/CLI integration.
|
Package gates require exact checks and installed off-screen GUI/CLI integration.
|
||||||
The portable ZIP receives source scans, an exact safe-member allowlist, and an
|
The portable ZIP receives source scans, an exact safe-member allowlist, and an
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
.\" SPDX-License-Identifier: AGPL-3.0-or-later
|
.\" SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
.\" Copyright (c) 2025-2026 Cristian Cezar Moisés
|
.\" Copyright (c) 2025-2026 Cristian Cezar Moisés
|
||||||
.TH ZUPT 1 "2026-08-31" "ZUPT 5.2.3" "User Commands"
|
.TH ZUPT 1 "2026-08-31" "ZUPT 5.2.8" "User Commands"
|
||||||
.
|
.
|
||||||
.SH NAME
|
.SH NAME
|
||||||
zupt \- source-built backup compression and authenticated-encryption utility
|
zupt \- source-built backup compression and authenticated-encryption utility
|
||||||
|
|
@ -89,7 +89,7 @@ Git and the upstream source tarball are source-only. Separately built CLI DEB,
|
||||||
binary RPM, SRPM, notice-bearing Linux tar.xz, Windows ZIP, and macOS DMG assets
|
binary RPM, SRPM, notice-bearing Linux tar.xz, Windows ZIP, and macOS DMG assets
|
||||||
may be published from the immutable tag only after their target-specific gates
|
may be published from the immutable tag only after their target-specific gates
|
||||||
pass; they never enter Git or the source tarball. An AppImage is not promoted
|
pass; they never enter Git or the source tarball. An AppImage is not promoted
|
||||||
for 5.2.3; neither are AppDir/Flatpak bundles, GUI platform installers, or bare
|
for 5.2.8; neither are AppDir/Flatpak bundles, GUI platform installers, or bare
|
||||||
Linux/Windows executables. The Python/Qt frontend remains available as source;
|
Linux/Windows executables. The Python/Qt frontend remains available as source;
|
||||||
its gated architecture-independent DEB, noarch/source RPM, and source-only
|
its gated architecture-independent DEB, noarch/source RPM, and source-only
|
||||||
portable ZIP are included in the release claim. The portable ZIP contains no
|
portable ZIP are included in the release claim. The portable ZIP contains no
|
||||||
|
|
@ -477,7 +477,7 @@ then traverse below a pinned directory descriptor with no-follow operations.
|
||||||
Windows builds use handle-relative traversal and
|
Windows builds use handle-relative traversal and
|
||||||
no-replace publication for normal local Win32 destinations. Extended-length and
|
no-replace publication for normal local Win32 destinations. Extended-length and
|
||||||
device-namespace paths, raw UNC output roots, and mapped/network-drive output
|
device-namespace paths, raw UNC output roots, and mapped/network-drive output
|
||||||
are not supported in 5.2.3. Cross-compilation and Wine results are not native
|
are not supported in 5.2.8. Cross-compilation and Wine results are not native
|
||||||
Windows evidence; the native Windows package gate, including its Unicode round
|
Windows evidence; the native Windows package gate, including its Unicode round
|
||||||
trip, is separate and mandatory before publication.
|
trip, is separate and mandatory before publication.
|
||||||
.
|
.
|
||||||
|
|
@ -577,7 +577,7 @@ and the encrypted-dedup linear AAD sequence published through 5.2.1. The narrow
|
||||||
compatibility fixture is an actual v5.2.1 password-encrypted
|
compatibility fixture is an actual v5.2.1 password-encrypted
|
||||||
DATA/DATA/REF/DATA disk archive stored as hexadecimal text with source and hash
|
DATA/DATA/REF/DATA disk archive stored as hexadecimal text with source and hash
|
||||||
provenance. The candidate lists, tests, extracts, and restores that fixture
|
provenance. The candidate lists, tests, extracts, and restores that fixture
|
||||||
byte-exact. The exact 5.2.3 candidate must repeat the gate. It does not imply
|
byte-exact. The exact 5.2.8 candidate must repeat the gate. It does not imply
|
||||||
that a 5.2.1 reader
|
that a 5.2.1 reader
|
||||||
accepts every new 5.2.2 archive or that every historical encrypted mode was
|
accepts every new 5.2.2 archive or that every historical encrypted mode was
|
||||||
retested.
|
retested.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# ZUPT GUI
|
# ZUPT GUI
|
||||||
|
|
||||||
The ZUPT GUI is a Python/Qt front end for the ZUPT 5.2.3 command-line
|
The ZUPT GUI is a Python/Qt front end for the ZUPT 5.2.8 command-line
|
||||||
program. It starts the CLI as a subprocess; compression, archive parsing, and
|
program. It starts the CLI as a subprocess; compression, archive parsing, and
|
||||||
cryptography remain in the C program.
|
cryptography remain in the C program.
|
||||||
|
|
||||||
|
|
@ -83,10 +83,10 @@ operating systems and must be tested on the target system.
|
||||||
Release pages provide only these GUI artifacts after their separate package and
|
Release pages provide only these GUI artifacts after their separate package and
|
||||||
installed off-screen GUI/CLI integration gates pass:
|
installed off-screen GUI/CLI integration gates pass:
|
||||||
|
|
||||||
- `zupt-gui_5.2.3_all.deb`;
|
- `zupt-gui_5.2.8_all.deb`;
|
||||||
- `zupt-gui-5.2.3-1.noarch.rpm`;
|
- `zupt-gui-5.2.8-1.noarch.rpm`;
|
||||||
- `zupt-gui-5.2.3-1.src.rpm`;
|
- `zupt-gui-5.2.8-1.src.rpm`;
|
||||||
- `zupt-gui-5.2.3-portable.zip`.
|
- `zupt-gui-5.2.8-portable.zip`.
|
||||||
|
|
||||||
The DEB/RPM packages install the Python/Qt source and depend on the matching
|
The DEB/RPM packages install the Python/Qt source and depend on the matching
|
||||||
`zupt` CLI package. The portable ZIP contains source, launchers, icons, licenses,
|
`zupt` CLI package. The portable ZIP contains source, launchers, icons, licenses,
|
||||||
|
|
@ -97,7 +97,7 @@ An absent artifact did not pass its gate and must not be inferred from another
|
||||||
format's result.
|
format's result.
|
||||||
|
|
||||||
GUI AppImage, AppDir and Flatpak bundles, and Windows/macOS GUI installers are
|
GUI AppImage, AppDir and Flatpak bundles, and Windows/macOS GUI installers are
|
||||||
not promoted by the upstream 5.2.3 release gates.
|
not promoted by the upstream 5.2.8 release gates.
|
||||||
`packaging/build-gui-appimage.sh` is a downstream-only helper and fails unless
|
`packaging/build-gui-appimage.sh` is a downstream-only helper and fails unless
|
||||||
its operator supplies the exact verified runtime plus a complete
|
its operator supplies the exact verified runtime plus a complete
|
||||||
license/source-relink notice through `APPIMAGE_RUNTIME_COMPLIANCE_FILE`; that
|
license/source-relink notice through `APPIMAGE_RUNTIME_COMPLIANCE_FILE`; that
|
||||||
|
|
@ -110,7 +110,7 @@ notices. It fails unless the directory also has non-empty
|
||||||
`PYTHON-NOTICE.txt`, `PYINSTALLER-NOTICE.txt`, `QT-NOTICE.txt`, and either
|
`PYTHON-NOTICE.txt`, `PYINSTALLER-NOTICE.txt`, `QT-NOTICE.txt`, and either
|
||||||
`PYSIDE6-NOTICE.txt` or `PYQT6-NOTICE.txt`. The installer includes that
|
`PYSIDE6-NOTICE.txt` or `PYQT6-NOTICE.txt`. The installer includes that
|
||||||
directory together with every ZUPT license and notice. This requirement does
|
directory together with every ZUPT license and notice. This requirement does
|
||||||
not make the untested GUI installer a 5.2.3 release asset. The promoted Windows
|
not make the untested GUI installer a 5.2.8 release asset. The promoted Windows
|
||||||
ZIP and macOS DMG are CLI-only.
|
ZIP and macOS DMG are CLI-only.
|
||||||
|
|
||||||
Packaging recipes and scripts under `gui/packaging/` and `packaging/` are build
|
Packaging recipes and scripts under `gui/packaging/` and `packaging/` are build
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
Package: zupt-gui
|
Package: zupt-gui
|
||||||
Version: 5.2.3
|
Version: 5.2.8
|
||||||
Section: utils
|
Section: utils
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Architecture: all
|
Architecture: all
|
||||||
Depends: python3 (>= 3.9), python3-pyqt6 | python3-pyside6.qtwidgets, zupt (= 5.2.3)
|
Depends: python3 (>= 3.9), python3-pyqt6 | python3-pyside6.qtwidgets, zupt (= 5.2.8)
|
||||||
Maintainer: Cristian Cezar Moisés <sac@securityops.co>
|
Maintainer: Cristian Cezar Moisés <sac@securityops.co>
|
||||||
Homepage: https://github.com/cristiancmoises/zupt
|
Homepage: https://github.com/cristiancmoises/zupt
|
||||||
Description: Qt graphical interface for the ZUPT backup utility
|
Description: Qt graphical interface for the ZUPT backup utility
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ rem runtime files embedded by this local build.
|
||||||
setlocal EnableExtensions
|
setlocal EnableExtensions
|
||||||
for %%I in ("%~dp0\..\..\..") do set "REPO_ROOT=%%~fI"
|
for %%I in ("%~dp0\..\..\..") do set "REPO_ROOT=%%~fI"
|
||||||
set "VERSION=%~1"
|
set "VERSION=%~1"
|
||||||
if not defined VERSION set "VERSION=5.2.3"
|
if not defined VERSION set "VERSION=5.2.8"
|
||||||
if not defined ZUPT_DIST_DIR set "ZUPT_DIST_DIR=%TEMP%\zupt-release"
|
if not defined ZUPT_DIST_DIR set "ZUPT_DIST_DIR=%TEMP%\zupt-release"
|
||||||
if not defined ZUPT_CLI_EXE set "ZUPT_CLI_EXE=%REPO_ROOT%\zupt.exe"
|
if not defined ZUPT_CLI_EXE set "ZUPT_CLI_EXE=%REPO_ROOT%\zupt.exe"
|
||||||
set "WORK=%TEMP%\zupt-gui-build-%RANDOM%-%RANDOM%"
|
set "WORK=%TEMP%\zupt-gui-build-%RANDOM%-%RANDOM%"
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,12 @@ static inline int zupt_win_mkdir_utf8(const char *path) {
|
||||||
#define ZUPT_PRODUCT_TAGLINE "Post-quantum backup compression"
|
#define ZUPT_PRODUCT_TAGLINE "Post-quantum backup compression"
|
||||||
|
|
||||||
/* v5.2.3 corrects release packaging and CI; archive format remains v1.6. */
|
/* v5.2.3 corrects release packaging and CI; archive format remains v1.6. */
|
||||||
#define ZUPT_VERSION_STRING "5.2.3"
|
/* v5.2.4 makes package metadata checks CRLF-safe; format remains v1.6. */
|
||||||
|
/* v5.2.5 corrects the OBS service harness cwd; format remains v1.6. */
|
||||||
|
/* v5.2.6 corrects native release-gate portability; format remains v1.6. */
|
||||||
|
/* v5.2.7 corrects native test-harness portability; format remains v1.6. */
|
||||||
|
/* v5.2.8 hardens three path-race boundaries; format remains v1.6. */
|
||||||
|
#define ZUPT_VERSION_STRING "5.2.8"
|
||||||
/* Vendored codec release (upstream tag) — single source for display strings.
|
/* Vendored codec release (upstream tag) — single source for display strings.
|
||||||
* The codec's own VV_VERSION_* is its internal API version, not the release. */
|
* The codec's own VV_VERSION_* is its internal API version, not the release. */
|
||||||
#define ZUPT_CODEC_RELEASE "2.65.3"
|
#define ZUPT_CODEC_RELEASE "2.65.3"
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
umask 077
|
umask 077
|
||||||
|
|
||||||
VERSION=${VERSION:-5.2.3}
|
VERSION=${VERSION:-5.2.8}
|
||||||
PREFIX=${PREFIX:-/usr/local}
|
PREFIX=${PREFIX:-/usr/local}
|
||||||
|
|
||||||
echo "🔧 Installing ZUPT..."
|
echo "🔧 Installing ZUPT..."
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
# Test locally with `makepkg -s` after the release archive is published.
|
# Test locally with `makepkg -s` after the release archive is published.
|
||||||
|
|
||||||
pkgname=zupt
|
pkgname=zupt
|
||||||
pkgver=5.2.3
|
pkgver=5.2.8
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc='Pure-C11 post-quantum backup compression utility (AES-256-CTR + HMAC-SHA256 + ML-KEM-768 + X25519)'
|
pkgdesc='Pure-C11 post-quantum backup compression utility (AES-256-CTR + HMAC-SHA256 + ML-KEM-768 + X25519)'
|
||||||
arch=('x86_64')
|
arch=('x86_64')
|
||||||
|
|
@ -22,8 +22,8 @@ makedepends=('gcc' 'git' 'make')
|
||||||
checkdepends=('python')
|
checkdepends=('python')
|
||||||
|
|
||||||
source=("${pkgname}-${pkgver}.tar.gz::https://github.com/cristiancmoises/zupt/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
|
source=("${pkgname}-${pkgver}.tar.gz::https://github.com/cristiancmoises/zupt/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
|
||||||
# Byte-reproducible upstream v5.2.3 source archive.
|
# Byte-reproducible upstream v5.2.8 source archive.
|
||||||
sha256sums=('062f008751a4333bd88ad8a826b98066704b4cb9bc08a016f2f5083365b449d4')
|
sha256sums=('378b9506211545b9594cf0d38ac8955d9b1cac34eb6b379ae0ec26b84edb65f7')
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
cd "${pkgname}-${pkgver}"
|
cd "${pkgname}-${pkgver}"
|
||||||
|
|
|
||||||
|
|
@ -124,20 +124,49 @@ mapfile -t source_rpms < <(find "$top/SRPMS" -type f -name "zupt-gui-$version-*.
|
||||||
[[ ${#main_rpms[@]} -eq 1 ]] || die "expected one GUI RPM, found ${#main_rpms[@]}"
|
[[ ${#main_rpms[@]} -eq 1 ]] || die "expected one GUI RPM, found ${#main_rpms[@]}"
|
||||||
[[ ${#source_rpms[@]} -eq 1 ]] || die "expected one GUI source RPM, found ${#source_rpms[@]}"
|
[[ ${#source_rpms[@]} -eq 1 ]] || die "expected one GUI source RPM, found ${#source_rpms[@]}"
|
||||||
|
|
||||||
rpm -qpl "${main_rpms[0]}" >"$work/contents.txt"
|
main_rpm=${main_rpms[0]}
|
||||||
|
source_rpm=${source_rpms[0]}
|
||||||
|
[[ $(rpm -qp --qf '%{NAME}' "$main_rpm") == zupt-gui ]] || \
|
||||||
|
die 'GUI binary RPM name metadata is not zupt-gui'
|
||||||
|
[[ $(rpm -qp --qf '%{VERSION}' "$main_rpm") == "$version" ]] || \
|
||||||
|
die 'GUI binary RPM version metadata does not match the release'
|
||||||
|
[[ $(rpm -qp --qf '%{RELEASE}' "$main_rpm") == 1 ]] || \
|
||||||
|
die 'GUI binary RPM release metadata is not 1'
|
||||||
|
[[ $(rpm -qp --qf '%{SOURCEPACKAGE}' "$main_rpm") == '(none)' ]] || \
|
||||||
|
die 'GUI binary RPM is marked as a source package'
|
||||||
|
[[ $(rpm -qp --qf '%{SOURCERPM}' "$main_rpm") == "$(basename -- "$source_rpm")" ]] || \
|
||||||
|
die 'GUI binary RPM does not reference the matching source RPM'
|
||||||
|
[[ $(rpm -qp --qf '%{NAME}' "$source_rpm") == zupt-gui ]] || \
|
||||||
|
die 'GUI source RPM name metadata is not zupt-gui'
|
||||||
|
[[ $(rpm -qp --qf '%{VERSION}' "$source_rpm") == "$version" ]] || \
|
||||||
|
die 'GUI source RPM version metadata does not match the release'
|
||||||
|
[[ $(rpm -qp --qf '%{RELEASE}' "$source_rpm") == 1 ]] || \
|
||||||
|
die 'GUI source RPM release metadata is not 1'
|
||||||
|
[[ $(rpm -qp --qf '%{SOURCEPACKAGE}' "$source_rpm") == 1 ]] || \
|
||||||
|
die 'GUI source RPM is not marked as a source package'
|
||||||
|
[[ $(rpm -qp --qf '%{SOURCERPM}' "$source_rpm") == '(none)' ]] || \
|
||||||
|
die 'GUI source RPM unexpectedly references another source RPM'
|
||||||
|
mapfile -t source_members < <(rpm -qpl "$source_rpm" | sort)
|
||||||
|
expected_source_members=("zupt-gui-${version}.tar.gz" zupt-gui.spec)
|
||||||
|
mapfile -t expected_source_members < <(printf '%s\n' "${expected_source_members[@]}" | sort)
|
||||||
|
[[ ${#source_members[@]} -eq 2 && \
|
||||||
|
${source_members[*]} == "${expected_source_members[*]}" ]] || \
|
||||||
|
die 'GUI source RPM payload is not the exact Source0/spec pair'
|
||||||
|
|
||||||
|
rpm -qpl "$main_rpm" >"$work/contents.txt"
|
||||||
grep -q '^/usr/bin/zupt-gui$' "$work/contents.txt" || die 'GUI launcher missing from RPM'
|
grep -q '^/usr/bin/zupt-gui$' "$work/contents.txt" || die 'GUI launcher missing from RPM'
|
||||||
if grep -Eq '(^/usr/bin/vaptvupt-gui$|\.(o|obj|a|so|so\.[^/]+|dll|dylib|exe)$)' "$work/contents.txt"; then
|
if grep -Eq '(^/usr/bin/vaptvupt-gui$|\.(o|obj|a|so|so\.[^/]+|dll|dylib|exe)$)' "$work/contents.txt"; then
|
||||||
cat "$work/contents.txt" >&2
|
cat "$work/contents.txt" >&2
|
||||||
die 'forbidden compatibility alias or compiled artifact in GUI RPM'
|
die 'forbidden compatibility alias or compiled artifact in GUI RPM'
|
||||||
fi
|
fi
|
||||||
(cd -- "$extract" && rpm2cpio "${main_rpms[0]}" | cpio -idm --quiet)
|
(cd -- "$extract" && rpm2cpio "$main_rpm" | cpio -idm --quiet)
|
||||||
PYTHONDONTWRITEBYTECODE=1 python3 - <<PY
|
PYTHONDONTWRITEBYTECODE=1 python3 - <<PY
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
p = Path("$extract/usr/share/zupt-gui/zupt_gui.py")
|
p = Path("$extract/usr/share/zupt-gui/zupt_gui.py")
|
||||||
compile(p.read_text(encoding="utf-8"), str(p), "exec")
|
compile(p.read_text(encoding="utf-8"), str(p), "exec")
|
||||||
PY
|
PY
|
||||||
|
|
||||||
for artifact in "${main_rpms[0]}" "${source_rpms[0]}"; do
|
for artifact in "$main_rpm" "$source_rpm"; do
|
||||||
destination=$dist_dir/$(basename -- "$artifact")
|
destination=$dist_dir/$(basename -- "$artifact")
|
||||||
[[ ! -e $destination ]] || die "refusing to overwrite existing output: $destination"
|
[[ ! -e $destination ]] || die "refusing to overwrite existing output: $destination"
|
||||||
cp -- "$artifact" "$destination"
|
cp -- "$artifact" "$destination"
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,33 @@ mapfile -t source_rpms < <(find "$top/SRPMS" -type f -name "zupt-${version}-*.sr
|
||||||
[[ ${#source_rpms[@]} -eq 1 ]] || die "expected one source RPM, found ${#source_rpms[@]}"
|
[[ ${#source_rpms[@]} -eq 1 ]] || die "expected one source RPM, found ${#source_rpms[@]}"
|
||||||
source_rpm=${source_rpms[0]}
|
source_rpm=${source_rpms[0]}
|
||||||
|
|
||||||
|
[[ $(rpm -qp --qf '%{NAME}' "$main_rpm") == zupt ]] || \
|
||||||
|
die 'binary RPM name metadata is not zupt'
|
||||||
|
[[ $(rpm -qp --qf '%{VERSION}' "$main_rpm") == "$version" ]] || \
|
||||||
|
die 'binary RPM version metadata does not match the release'
|
||||||
|
[[ $(rpm -qp --qf '%{RELEASE}' "$main_rpm") == 0 ]] || \
|
||||||
|
die 'binary RPM release metadata is not 0'
|
||||||
|
[[ $(rpm -qp --qf '%{SOURCEPACKAGE}' "$main_rpm") == '(none)' ]] || \
|
||||||
|
die 'binary RPM is marked as a source package'
|
||||||
|
[[ $(rpm -qp --qf '%{SOURCERPM}' "$main_rpm") == "$(basename -- "$source_rpm")" ]] || \
|
||||||
|
die 'binary RPM does not reference the matching source RPM'
|
||||||
|
[[ $(rpm -qp --qf '%{NAME}' "$source_rpm") == zupt ]] || \
|
||||||
|
die 'source RPM name metadata is not zupt'
|
||||||
|
[[ $(rpm -qp --qf '%{VERSION}' "$source_rpm") == "$version" ]] || \
|
||||||
|
die 'source RPM version metadata does not match the release'
|
||||||
|
[[ $(rpm -qp --qf '%{RELEASE}' "$source_rpm") == 0 ]] || \
|
||||||
|
die 'source RPM release metadata is not 0'
|
||||||
|
[[ $(rpm -qp --qf '%{SOURCEPACKAGE}' "$source_rpm") == 1 ]] || \
|
||||||
|
die 'source RPM is not marked as a source package'
|
||||||
|
[[ $(rpm -qp --qf '%{SOURCERPM}' "$source_rpm") == '(none)' ]] || \
|
||||||
|
die 'source RPM unexpectedly references another source RPM'
|
||||||
|
mapfile -t source_members < <(rpm -qpl "$source_rpm" | sort)
|
||||||
|
expected_source_members=("zupt-${version}.tar.gz" zupt.spec)
|
||||||
|
mapfile -t expected_source_members < <(printf '%s\n' "${expected_source_members[@]}" | sort)
|
||||||
|
[[ ${#source_members[@]} -eq 2 && \
|
||||||
|
${source_members[*]} == "${expected_source_members[*]}" ]] || \
|
||||||
|
die 'source RPM payload is not the exact Source0/spec pair'
|
||||||
|
|
||||||
rpm -qpi "$main_rpm" >/dev/null
|
rpm -qpi "$main_rpm" >/dev/null
|
||||||
rpm -qpl "$main_rpm" > "$work/contents.txt"
|
rpm -qpl "$main_rpm" > "$work/contents.txt"
|
||||||
if grep -Eq '(^/usr/bin/vaptvupt$|\.(o|obj|a|so|so\.[^/]+|dll|dylib)$)' "$work/contents.txt"; then
|
if grep -Eq '(^/usr/bin/vaptvupt$|\.(o|obj|a|so|so\.[^/]+|dll|dylib)$)' "$work/contents.txt"; then
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,54 @@
|
||||||
|
zupt (5.2.8-1) UNRELEASED; urgency=medium
|
||||||
|
|
||||||
|
* Close CodeQL High path-race findings in SDK key publication, disk-restore
|
||||||
|
target handling, and benchmark workspace cleanup.
|
||||||
|
* Treat a filesystem refusal to create the macOS raw-C1 scanner fixture as
|
||||||
|
an explicit skip; reject redirected Windows prompts before _getch; and run
|
||||||
|
sdk-test in the release and hosted Linux gates.
|
||||||
|
* Preserve immutable, unpromoted v5.2.7 run 33445470664: 13 jobs succeeded,
|
||||||
|
macOS failed the raw-C1 fixture, and Windows was cancelled after the hosted
|
||||||
|
job stalled; a MinGW/Wine reproduction isolated redirected _getch entry.
|
||||||
|
Require fresh 5.2.8 evidence.
|
||||||
|
|
||||||
|
-- Cristian Cezar Moisés <sac@securityops.co> Mon, 31 Aug 2026 23:30:00 +0000
|
||||||
|
|
||||||
|
zupt (5.2.7-1) UNRELEASED; urgency=medium
|
||||||
|
|
||||||
|
* Scope SHA-NI test helpers to supported x86 builds so macOS arm64 strict
|
||||||
|
compilation does not fail on unused declarations.
|
||||||
|
* Preserve safe UTF-8 fixture bytes across the Windows argv boundary.
|
||||||
|
* Preserve the immutable, unpromoted 5.2.6 history and require fresh 5.2.7
|
||||||
|
package, checksum, native-platform, OBS, and promotion gates.
|
||||||
|
|
||||||
|
-- Cristian Cezar Moisés <sac@securityops.co> Mon, 31 Aug 2026 23:00:00 +0000
|
||||||
|
|
||||||
|
zupt (5.2.6-1) UNRELEASED; urgency=medium
|
||||||
|
|
||||||
|
* Use the compiler-resistant volatile wipe fallback on macOS and NetBSD, and
|
||||||
|
make the source scanner's empty-array handling compatible with Bash 3.2.
|
||||||
|
* Preserve hostile archive-path fixture bytes exactly on Windows.
|
||||||
|
* Preserve the immutable, unpromoted 5.2.5 history and require fresh 5.2.6
|
||||||
|
package, checksum, native-platform, OBS, and promotion gates.
|
||||||
|
|
||||||
|
-- Cristian Cezar Moisés <sac@securityops.co> Mon, 31 Aug 2026 21:30:00 +0000
|
||||||
|
|
||||||
|
zupt (5.2.5-1) UNRELEASED; urgency=medium
|
||||||
|
|
||||||
|
* Run the standalone OBS source-service chain from its isolated working
|
||||||
|
directory and add a packaging-policy regression for that contract.
|
||||||
|
* Preserve the immutable, unpromoted 5.2.4 history and require fresh 5.2.5
|
||||||
|
package, checksum, native-platform, and promotion gates.
|
||||||
|
|
||||||
|
-- Cristian Cezar Moisés <sac@securityops.co> Mon, 31 Aug 2026 19:55:00 +0000
|
||||||
|
|
||||||
|
zupt (5.2.4-1) UNRELEASED; urgency=medium
|
||||||
|
|
||||||
|
* Make the static Windows GUI package-version check robust to canonical
|
||||||
|
CRLF checkouts.
|
||||||
|
* Advance source-only package metadata and prepare final archive checksums.
|
||||||
|
|
||||||
|
-- Cristian Cezar Moisés <sac@securityops.co> Mon, 31 Aug 2026 18:55:00 +0000
|
||||||
|
|
||||||
zupt (5.2.3-1) UNRELEASED; urgency=medium
|
zupt (5.2.3-1) UNRELEASED; urgency=medium
|
||||||
|
|
||||||
* Derive package checks from the upstream version header and stabilize the
|
* Derive package checks from the upstream version header and stabilize the
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@
|
||||||
xcb-util-renderutil xcb-util-wm xcb-util-cursor
|
xcb-util-renderutil xcb-util-wm xcb-util-cursor
|
||||||
libinput-minimal mtdev libevdev eudev))
|
libinput-minimal mtdev libevdev eudev))
|
||||||
|
|
||||||
(define %zupt-version "5.2.3")
|
(define %zupt-version "5.2.8")
|
||||||
|
|
||||||
(define %zupt-source
|
(define %zupt-source
|
||||||
(origin
|
(origin
|
||||||
|
|
@ -72,7 +72,7 @@
|
||||||
"/releases/download/v" %zupt-version
|
"/releases/download/v" %zupt-version
|
||||||
"/zupt-" %zupt-version ".tar.gz"))
|
"/zupt-" %zupt-version ".tar.gz"))
|
||||||
(sha256
|
(sha256
|
||||||
(base32 "1m29nijk627my8ba025wp564nw36h2wjda6qibc3ncx4a63h0bq6"))))
|
(base32 "1xv5vd7bh9pcw2d3fszb6jn1r6sxjp48mlzh9icvji8m4439b2rp"))))
|
||||||
|
|
||||||
(define-public zupt
|
(define-public zupt
|
||||||
(package
|
(package
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,9 @@
|
||||||
class Zupt < Formula
|
class Zupt < Formula
|
||||||
desc "Post-quantum backup compression utility (ML-KEM-768 + AES-256-CTR + HMAC-SHA256)"
|
desc "Post-quantum backup compression utility (ML-KEM-768 + AES-256-CTR + HMAC-SHA256)"
|
||||||
homepage "https://github.com/cristiancmoises/zupt"
|
homepage "https://github.com/cristiancmoises/zupt"
|
||||||
url "https://github.com/cristiancmoises/zupt/releases/download/v5.2.3/zupt-5.2.3.tar.gz"
|
url "https://github.com/cristiancmoises/zupt/releases/download/v5.2.8/zupt-5.2.8.tar.gz"
|
||||||
version "5.2.3"
|
version "5.2.8"
|
||||||
sha256 "062f008751a4333bd88ad8a826b98066704b4cb9bc08a016f2f5083365b449d4"
|
sha256 "378b9506211545b9594cf0d38ac8955d9b1cac34eb6b379ae0ec26b84edb65f7"
|
||||||
license all_of: ["AGPL-3.0-or-later", "GPL-3.0-or-later", "BSD-2-Clause", "BSD-3-Clause", "CC0-1.0"]
|
license all_of: ["AGPL-3.0-or-later", "GPL-3.0-or-later", "BSD-2-Clause", "BSD-3-Clause", "CC0-1.0"]
|
||||||
|
|
||||||
depends_on "python@3.12" => :test # only for test-suite tamper harness
|
depends_on "python@3.12" => :test # only for test-suite tamper harness
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
# nix flake check # lint the flake
|
# nix flake check # lint the flake
|
||||||
#
|
#
|
||||||
# To consume from another flake:
|
# To consume from another flake:
|
||||||
# inputs.zupt.url = "github:cristiancmoises/zupt/v5.2.3";
|
# inputs.zupt.url = "github:cristiancmoises/zupt/v5.2.8";
|
||||||
# ...packages.x86_64-linux.default = inputs.zupt.packages.x86_64-linux.zupt;
|
# ...packages.x86_64-linux.default = inputs.zupt.packages.x86_64-linux.zupt;
|
||||||
#
|
#
|
||||||
# `make dist` has its own reproducibility gate. This development flake has no
|
# `make dist` has its own reproducibility gate. This development flake has no
|
||||||
|
|
@ -30,7 +30,7 @@
|
||||||
|
|
||||||
zupt = pkgs.stdenv.mkDerivation {
|
zupt = pkgs.stdenv.mkDerivation {
|
||||||
pname = "zupt";
|
pname = "zupt";
|
||||||
version = "5.2.3";
|
version = "5.2.8";
|
||||||
|
|
||||||
# When publishing, replace this with `fetchurl` against the
|
# When publishing, replace this with `fetchurl` against the
|
||||||
# release tarball. For local development the flake assumes it
|
# release tarball. For local development the flake assumes it
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# ZUPT 5.2.3 for openSUSE Build Service
|
# ZUPT 5.2.8 for openSUSE Build Service
|
||||||
|
|
||||||
This directory is the upstream, source-only OBS recipe for ZUPT. It is a
|
This directory is the upstream, source-only OBS recipe for ZUPT. It is a
|
||||||
handoff for the downstream maintainer; its presence does not mean that the
|
handoff for the downstream maintainer; its presence does not mean that the
|
||||||
|
|
@ -10,13 +10,14 @@ changes in this handoff. Alessandro de Oliveira Faria (Cabelo) is credited only
|
||||||
as the openSUSE collaborator and downstream OBS package maintainer: he reviews
|
as the openSUSE collaborator and downstream OBS package maintainer: he reviews
|
||||||
the handoff, commits it through the portal/project he maintains, and may make
|
the handoff, commits it through the portal/project he maintains, and may make
|
||||||
the openSUSE-side adjustments he considers necessary. This role does not
|
the openSUSE-side adjustments he considers necessary. This role does not
|
||||||
attribute upstream code or the 5.2.2/5.2.3 upstream changes to Cabelo.
|
attribute upstream code or the
|
||||||
|
5.2.2/5.2.3/5.2.4/5.2.5/5.2.6/5.2.7/5.2.8 upstream changes to Cabelo.
|
||||||
|
|
||||||
## Files and source policy
|
## Files and source policy
|
||||||
|
|
||||||
| File | Purpose |
|
| File | Purpose |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `_service` | Fetch the immutable `v5.2.3` tag and create `Source0` at build time. |
|
| `_service` | Fetch the immutable `v5.2.8` tag and create `Source0` at build time. |
|
||||||
| `zupt.spec` | Build and test the CLI with optional external system integrations disabled. |
|
| `zupt.spec` | Build and test the CLI with optional external system integrations disabled. |
|
||||||
| `zupt.changes` | openSUSE-format package history. |
|
| `zupt.changes` | openSUSE-format package history. |
|
||||||
| `source-audit.sh` | Handoff wrapper for the repository scanner; run it from the complete handoff tree. |
|
| `source-audit.sh` | Handoff wrapper for the repository scanner; run it from the complete handoff tree. |
|
||||||
|
|
@ -29,11 +30,11 @@ https://github.com/cristiancmoises/zupt.git
|
||||||
```
|
```
|
||||||
|
|
||||||
`obs_scm` stores an `.obscpio` plus `.obsinfo`. The `tar` and `recompress`
|
`obs_scm` stores an `.obscpio` plus `.obsinfo`. The `tar` and `recompress`
|
||||||
services reconstruct `zupt-5.2.3.tar.gz` inside the build environment, which
|
services reconstruct `zupt-5.2.8.tar.gz` inside the build environment, which
|
||||||
matches `Source0` in the spec.
|
matches `Source0` in the spec.
|
||||||
|
|
||||||
This source policy does not prohibit separately built release-page packages.
|
This source policy does not prohibit separately built release-page packages.
|
||||||
The upstream 5.2.3 gates may publish the CLI source tarball, DEB, binary RPM,
|
The upstream 5.2.8 gates may publish the CLI source tarball, DEB, binary RPM,
|
||||||
SRPM, notice-bearing Linux tar.xz, Windows ZIP, and macOS DMG, together with a
|
SRPM, notice-bearing Linux tar.xz, Windows ZIP, and macOS DMG, together with a
|
||||||
GUI DEB, noarch RPM, GUI SRPM, and source-only portable GUI ZIP after each
|
GUI DEB, noarch RPM, GUI SRPM, and source-only portable GUI ZIP after each
|
||||||
format-specific test succeeds. None of those files is an OBS `Source0` input
|
format-specific test succeeds. None of those files is an OBS `Source0` input
|
||||||
|
|
@ -138,7 +139,7 @@ reconstructed by the build-time services. Neither `%build` nor `%check` may
|
||||||
access the network.
|
access the network.
|
||||||
|
|
||||||
For a source RPM check outside OBS, place the service-produced
|
For a source RPM check outside OBS, place the service-produced
|
||||||
`zupt-5.2.3.tar.gz` next to the spec and use a disposable RPM build tree:
|
`zupt-5.2.8.tar.gz` next to the spec and use a disposable RPM build tree:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
rpm_top=$(mktemp -d)
|
rpm_top=$(mktemp -d)
|
||||||
|
|
@ -166,11 +167,93 @@ unavailable rather than passing it. Earlier off-screen GUI smoke evidence is
|
||||||
supporting evidence, not an exact-commit package result.
|
supporting evidence, not an exact-commit package result.
|
||||||
|
|
||||||
Post-tag CI integration failures prevented 5.2.2 promotion. These historical
|
Post-tag CI integration failures prevented 5.2.2 promotion. These historical
|
||||||
local results do not establish 5.2.3, native Windows or macOS success, hosted
|
local results do not establish 5.2.8, native Windows or macOS success, hosted
|
||||||
GitHub CI/release promotion, authenticated OBS acceptance, or resolution of the
|
GitHub CI/release promotion, authenticated OBS acceptance, or resolution of the
|
||||||
automatic openSUSE `debugsource` rpmlint `no-binary` finding. The exact 5.2.3
|
automatic openSUSE `debugsource` rpmlint `no-binary` finding. The immutable
|
||||||
candidate must repeat every applicable gate; those gates remain pending until
|
5.2.3 candidate was not promoted because its source-policy test assumed LF for
|
||||||
recorded otherwise.
|
a Windows `.bat` file checked out as CRLF.
|
||||||
|
|
||||||
|
## Prior 5.2.4 exact-tag source-service evidence
|
||||||
|
|
||||||
|
The immutable v5.2.4 candidate was not promoted. Exact-tag GitHub Actions run
|
||||||
|
`33431386002` recorded 12 successful jobs and one failed openSUSE job. That job's
|
||||||
|
standalone `Serviceinfo` harness passed the service directory to the executor
|
||||||
|
but did not make it the process working directory; dependent native Windows and
|
||||||
|
macOS jobs were skipped.
|
||||||
|
|
||||||
|
A disposable local openSUSE Tumbleweed reproduction independently resolved
|
||||||
|
`refs/tags/v5.2.4` to the tagged commit. With `osc` 1.27.3,
|
||||||
|
`obs-service-obs_scm` 0.12.4, `obs-service-tar` 0.12.4, and
|
||||||
|
`obs-service-recompress` 0.5.2 installed, the same executor completed
|
||||||
|
`obs_scm`, `tar`, and `recompress` after `os.chdir(service_dir)`. It produced
|
||||||
|
exactly one `zupt-5.2.4.tar.gz`; its SHA-256 was
|
||||||
|
`aa68a58fc2e88ee92296542de1f189e2b8a803154d832fb04d5296b25acaef8f`, and the
|
||||||
|
source scanner reported `PASS source-only: 204 files, 1 archives`.
|
||||||
|
|
||||||
|
This result establishes that the explicit tag revision works and isolates a
|
||||||
|
release/test harness defect. It does not change the product, archive format,
|
||||||
|
cryptography, codec, or SDK ABI; it does not make skipped native jobs pass or
|
||||||
|
establish authenticated OBS/Factory acceptance. No v5.2.4 evidence transferred
|
||||||
|
automatically to v5.2.8; the exact candidate later repeated every applicable
|
||||||
|
upstream gate in run `33456209269`, as recorded below. The automatic openSUSE
|
||||||
|
`debugsource` rpmlint `no-binary` finding remains unresolved and unsuppressed.
|
||||||
|
|
||||||
|
## Prior 5.2.5 exact-tag native-gate evidence
|
||||||
|
|
||||||
|
The immutable v5.2.5 candidate was not promoted. Exact-tag GitHub Actions run
|
||||||
|
`33434986357` completed 13 jobs successfully and failed the native Windows and
|
||||||
|
macOS jobs. Windows exposed a hostile-path fixture that did not preserve its
|
||||||
|
requested bytes across the command-line boundary; macOS exposed the unsupported
|
||||||
|
`explicit_bzero` assumption and Bash 3.2 empty-array handling. The 5.2.6
|
||||||
|
corrections address those release/test integration defects without an archive,
|
||||||
|
cryptographic, codec, or SDK ABI change.
|
||||||
|
|
||||||
|
## Prior 5.2.6 exact-tag native-gate evidence
|
||||||
|
|
||||||
|
The immutable v5.2.6 candidate was not promoted. Exact-tag GitHub Actions run
|
||||||
|
`33442264243` completed 13 jobs successfully and failed two native jobs. The
|
||||||
|
macOS arm64 SHA-NI test build treated unused x86-only helper declarations as
|
||||||
|
errors under `-Werror`; Windows argv transcoding aborted the safe printable
|
||||||
|
UTF-8 fixture before its intended path assertions. The 5.2.7 changes correct
|
||||||
|
those test-harness boundaries without an archive-format, cryptographic, codec,
|
||||||
|
or SDK ABI change. They do not establish 5.2.8 hosted, native, OBS, or promotion
|
||||||
|
evidence.
|
||||||
|
|
||||||
|
## Prior 5.2.7 exact-tag native-gate evidence
|
||||||
|
|
||||||
|
The immutable v5.2.7 candidate was not promoted. Exact-tag GitHub Actions run
|
||||||
|
`33445470664` concluded `cancelled` at `2026-08-31T23:11:19Z`, with 13
|
||||||
|
successful jobs, one failed macOS job, and one cancelled Windows job. macOS
|
||||||
|
rejected creation of the raw-C1 scanner fixture
|
||||||
|
with `EILSEQ`; the hosted Windows job stalled in `make check`, and a MinGW/Wine
|
||||||
|
reproduction isolated the cause to a redirected password prompt entering
|
||||||
|
`_getch`. Version 5.2.8 makes those test
|
||||||
|
boundaries fail or skip without hanging, addresses CodeQL High #5/#6/#7 in SDK
|
||||||
|
key publication, disk restore, and benchmark cleanup, and adds `sdk-test` to
|
||||||
|
release and hosted Linux gates. None of those changes establishes an exact
|
||||||
|
5.2.8 OBS, native, hosted-CI, or promotion result.
|
||||||
|
|
||||||
|
## 5.2.8 exact-tag upstream package evidence
|
||||||
|
|
||||||
|
Manually dispatched exact-tag GitHub Actions run `33456209269` passed all 15
|
||||||
|
jobs at `ebb9ab3aa1d42c50030ca02883f6162dc4771fe1`. Its openSUSE Tumbleweed
|
||||||
|
x86_64 job parsed and normalized the spec, executed the pinned standalone OBS
|
||||||
|
source-service chain, source-scanned the resulting archive, built the binary
|
||||||
|
RPM and genuine SRPM, ran `rpmlint` without suppressions, and completed the
|
||||||
|
install/round-trip/uninstall test. The canonical source archive is 798296 bytes
|
||||||
|
with SHA-256
|
||||||
|
`378b9506211545b9594cf0d38ac8955d9b1cac34eb6b379ae0ec26b84edb65f7`.
|
||||||
|
|
||||||
|
Promotion run `33457868306` published the exact tested binary RPM and SRPM with
|
||||||
|
the other gated assets. The source package is identified by
|
||||||
|
`%{SOURCEPACKAGE}=1` and an absent `%{SOURCERPM}`; its `%{ARCH}` legitimately
|
||||||
|
reflects the spec's build architecture and is not the SRPM discriminator.
|
||||||
|
Repository, Git archive, and upstream source tarball scans remain binary-free.
|
||||||
|
|
||||||
|
This is upstream local-service and package evidence, not a claim that the
|
||||||
|
package was submitted to or accepted by openSUSE Factory, nor a result for the
|
||||||
|
full set of automatically generated OBS debug packages or any untested
|
||||||
|
architecture.
|
||||||
|
|
||||||
## Prior openSUSE packaging validation
|
## Prior openSUSE packaging validation
|
||||||
|
|
||||||
|
|
@ -209,11 +292,11 @@ gate.
|
||||||
## Handoff procedure for Alessandro/Cabelo
|
## Handoff procedure for Alessandro/Cabelo
|
||||||
|
|
||||||
1. Upstream completes every applicable pre-tag source and local audit gate,
|
1. Upstream completes every applicable pre-tag source and local audit gate,
|
||||||
then creates and verifies the annotated `v5.2.3` tag. Exact-tag hosted,
|
then creates and verifies the annotated `v5.2.8` tag. Exact-tag hosted,
|
||||||
native-platform, package, and promotion gates must pass before release or
|
native-platform, package, and promotion gates must pass before release or
|
||||||
downstream handoff; the tag itself is never moved to repair a failure.
|
downstream handoff; the tag itself is never moved to repair a failure.
|
||||||
2. With Git, `file`, bsdtar, tar, zip, unzip and SHA-256 tools installed, run
|
2. With Git, `file`, bsdtar, tar, zip, unzip and SHA-256 tools installed, run
|
||||||
`scripts/export-opensuse-package.sh v5.2.3`. Verify the reported ZIP and
|
`scripts/export-opensuse-package.sh v5.2.8`. Verify the reported ZIP and
|
||||||
SHA-256 outside the Git index. The handoff includes both
|
SHA-256 outside the Git index. The handoff includes both
|
||||||
`packaging/opensuse/source-audit.sh` and its required
|
`packaging/opensuse/source-audit.sh` and its required
|
||||||
`scripts/check-source-only.sh`; keep that relative layout while auditing.
|
`scripts/check-source-only.sh`; keep that relative layout while auditing.
|
||||||
|
|
@ -225,7 +308,7 @@ gate.
|
||||||
```
|
```
|
||||||
|
|
||||||
4. From the extracted handoff root, run
|
4. From the extracted handoff root, run
|
||||||
`packaging/opensuse/source-audit.sh --archive /path/to/zupt-5.2.3.tar.gz`.
|
`packaging/opensuse/source-audit.sh --archive /path/to/zupt-5.2.8.tar.gz`.
|
||||||
Then copy `_service`, `zupt.spec`, `zupt.changes` and `README.md`
|
Then copy `_service`, `zupt.spec`, `zupt.changes` and `README.md`
|
||||||
into the flat OBS package checkout. The audit wrapper is not an OBS build
|
into the flat OBS package checkout. The audit wrapper is not an OBS build
|
||||||
source and must not be copied without its companion `scripts/` directory.
|
source and must not be copied without its companion `scripts/` directory.
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<service name="obs_scm" mode="manual">
|
<service name="obs_scm" mode="manual">
|
||||||
<param name="url">https://github.com/cristiancmoises/zupt.git</param>
|
<param name="url">https://github.com/cristiancmoises/zupt.git</param>
|
||||||
<param name="scm">git</param>
|
<param name="scm">git</param>
|
||||||
<param name="revision">refs/tags/v5.2.3</param>
|
<param name="revision">refs/tags/v5.2.8</param>
|
||||||
<param name="versionformat">@PARENT_TAG@</param>
|
<param name="versionformat">@PARENT_TAG@</param>
|
||||||
<param name="versionrewrite-pattern">^v(.*)$</param>
|
<param name="versionrewrite-pattern">^v(.*)$</param>
|
||||||
<param name="versionrewrite-replacement">\1</param>
|
<param name="versionrewrite-replacement">\1</param>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,56 @@
|
||||||
|
-------------------------------------------------------------------
|
||||||
|
Mon Aug 31 23:30:00 UTC 2026 - Cristian Cezar Moisés <sac@securityops.co>
|
||||||
|
|
||||||
|
- Update to 5.2.8:
|
||||||
|
* Close CodeQL High path-race findings in SDK key publication,
|
||||||
|
descriptor-first disk restore, and benchmark workspace cleanup.
|
||||||
|
* Make the raw-C1 scanner fixture explicitly skip filesystems that reject
|
||||||
|
the byte with EILSEQ, reject redirected Windows prompts before _getch, and
|
||||||
|
add sdk-test to release/hosted Linux gates.
|
||||||
|
* Preserve immutable, unpromoted v5.2.7 run 33445470664: 13 jobs succeeded,
|
||||||
|
macOS failed the raw-C1 fixture, and Windows was cancelled after the hosted
|
||||||
|
job stalled; a MinGW/Wine reproduction isolated redirected _getch entry.
|
||||||
|
* Pin the OBS source service to the immutable v5.2.8 tag and require fresh
|
||||||
|
exact-candidate evidence before promotion.
|
||||||
|
|
||||||
|
-------------------------------------------------------------------
|
||||||
|
Mon Aug 31 23:00:00 UTC 2026 - Cristian Cezar Moisés <sac@securityops.co>
|
||||||
|
|
||||||
|
- Update to 5.2.7:
|
||||||
|
* Scope SHA-NI test helpers away from unsupported macOS arm64 builds.
|
||||||
|
* Preserve safe UTF-8 fixture bytes across the Windows argv boundary.
|
||||||
|
* Preserve immutable, unpromoted 5.2.6 history and require fresh 5.2.7 gates.
|
||||||
|
* Pin the OBS source service to the immutable v5.2.7 tag.
|
||||||
|
|
||||||
|
-------------------------------------------------------------------
|
||||||
|
Mon Aug 31 21:30:00 UTC 2026 - Cristian Cezar Moisés <sac@securityops.co>
|
||||||
|
|
||||||
|
- Update to 5.2.6:
|
||||||
|
* Use the compiler-resistant volatile wipe fallback on macOS and NetBSD.
|
||||||
|
* Make source-scanner empty-array handling compatible with Bash 3.2.
|
||||||
|
* Preserve hostile archive-path fixture bytes exactly on Windows.
|
||||||
|
* Preserve immutable, unpromoted 5.2.5 history and require fresh 5.2.6 gates.
|
||||||
|
* Pin the OBS source service to the immutable v5.2.6 tag.
|
||||||
|
|
||||||
|
-------------------------------------------------------------------
|
||||||
|
Mon Aug 31 19:55:00 UTC 2026 - Cristian Cezar Moisés <sac@securityops.co>
|
||||||
|
|
||||||
|
- Update to 5.2.5:
|
||||||
|
* Run the standalone OBS source-service chain from its isolated working
|
||||||
|
directory so downstream services can find .obsinfo.
|
||||||
|
* Add a packaging-policy regression for the executor working directory.
|
||||||
|
* Preserve immutable, unpromoted 5.2.4 history and require fresh 5.2.5 gates.
|
||||||
|
* Pin the OBS source service to the immutable v5.2.5 tag.
|
||||||
|
|
||||||
|
-------------------------------------------------------------------
|
||||||
|
Mon Aug 31 18:55:00 UTC 2026 - Cristian Cezar Moisés <sac@securityops.co>
|
||||||
|
|
||||||
|
- Update to 5.2.4:
|
||||||
|
* Make the static Windows GUI package-version check robust to canonical
|
||||||
|
CRLF checkouts.
|
||||||
|
* Advance source-only package metadata and prepare final archive hashes.
|
||||||
|
* Pin the OBS source service to the immutable v5.2.4 tag.
|
||||||
|
|
||||||
-------------------------------------------------------------------
|
-------------------------------------------------------------------
|
||||||
Mon Aug 31 18:15:00 UTC 2026 - Cristian Cezar Moisés <sac@securityops.co>
|
Mon Aug 31 18:15:00 UTC 2026 - Cristian Cezar Moisés <sac@securityops.co>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
Name: zupt
|
Name: zupt
|
||||||
Version: 5.2.3
|
Version: 5.2.8
|
||||||
Release: 0
|
Release: 0
|
||||||
Summary: Backup compression with authenticated and post-quantum encryption
|
Summary: Backup compression with authenticated and post-quantum encryption
|
||||||
License: AGPL-3.0-or-later AND GPL-3.0-or-later AND BSD-2-Clause AND BSD-3-Clause AND CC0-1.0
|
License: AGPL-3.0-or-later AND GPL-3.0-or-later AND BSD-2-Clause AND BSD-3-Clause AND CC0-1.0
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ Requirements
|
||||||
------------
|
------------
|
||||||
1. Python 3.9 or newer.
|
1. Python 3.9 or newer.
|
||||||
2. PySide6 6.5 or newer, or a compatible PyQt6 package.
|
2. PySide6 6.5 or newer, or a compatible PyQt6 package.
|
||||||
3. ZUPT 5.2.3, installed as `zupt` on PATH or placed beside the launcher
|
3. ZUPT 5.2.8, installed as `zupt` on PATH or placed beside the launcher
|
||||||
(`zupt.exe` on Windows). A local command must have been built
|
(`zupt.exe` on Windows). A local command must have been built
|
||||||
and tested independently; this bundle never downloads one.
|
and tested independently; this bundle never downloads one.
|
||||||
|
|
||||||
|
|
@ -46,7 +46,7 @@ Troubleshooting
|
||||||
---------------
|
---------------
|
||||||
* "requires PySide6 or PyQt6": install one Qt binding through your operating
|
* "requires PySide6 or PyQt6": install one Qt binding through your operating
|
||||||
system package manager or another trusted, preconfigured Python source.
|
system package manager or another trusted, preconfigured Python source.
|
||||||
* "zupt not found": install ZUPT 5.2.3 or place its command beside
|
* "zupt not found": install ZUPT 5.2.8 or place its command beside
|
||||||
the launcher.
|
the launcher.
|
||||||
* Set ZUPT_DEBUG=1 to print command-discovery diagnostics to stderr.
|
* Set ZUPT_DEBUG=1 to print command-discovery diagnostics to stderr.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
# installed smoke test.
|
# installed smoke test.
|
||||||
|
|
||||||
Name: zupt
|
Name: zupt
|
||||||
Version: 5.2.3
|
Version: 5.2.8
|
||||||
Release: 1%{?dist}
|
Release: 1%{?dist}
|
||||||
Summary: Backup compression with authenticated and post-quantum encryption
|
Summary: Backup compression with authenticated and post-quantum encryption
|
||||||
|
|
||||||
|
|
@ -101,6 +101,35 @@ comments. Plain archives use non-cryptographic checksums.
|
||||||
%endif
|
%endif
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Mon Aug 31 2026 Cristian Cezar Moisés <sac@securityops.co> - 5.2.8-1
|
||||||
|
- Close CodeQL High path-race findings in SDK key save, disk restore, and
|
||||||
|
benchmark cleanup; add the SDK gate, portable raw-C1 fixture handling, and
|
||||||
|
redirected Windows password-prompt rejection.
|
||||||
|
- Preserve immutable, unpromoted v5.2.7 run 33445470664: 13 jobs succeeded,
|
||||||
|
macOS failed the raw-C1 fixture, and Windows was cancelled after the hosted
|
||||||
|
job stalled; a MinGW/Wine reproduction isolated redirected _getch entry.
|
||||||
|
- Require fresh 5.2.8 gates.
|
||||||
|
|
||||||
|
* Mon Aug 31 2026 Cristian Cezar Moisés <sac@securityops.co> - 5.2.7-1
|
||||||
|
- Correct native test integration: scope SHA-NI helpers away from macOS arm64
|
||||||
|
and preserve safe UTF-8 fixture bytes across the Windows argv boundary.
|
||||||
|
- Preserve immutable, unpromoted 5.2.6 history and require fresh 5.2.7 gates.
|
||||||
|
|
||||||
|
* Mon Aug 31 2026 Cristian Cezar Moisés <sac@securityops.co> - 5.2.6-1
|
||||||
|
- Correct native release gates: use the secure volatile wipe fallback on
|
||||||
|
macOS and NetBSD, support Bash 3.2 empty arrays in the source scanner, and
|
||||||
|
preserve hostile archive-path fixture bytes exactly on Windows.
|
||||||
|
- Preserve immutable, unpromoted 5.2.5 history and require fresh 5.2.6 gates.
|
||||||
|
|
||||||
|
* Mon Aug 31 2026 Cristian Cezar Moisés <sac@securityops.co> - 5.2.5-1
|
||||||
|
- Run the standalone OBS source-service chain from its isolated working
|
||||||
|
directory and add a packaging-policy regression for that contract.
|
||||||
|
- Preserve immutable, unpromoted 5.2.4 history and require fresh 5.2.5 gates.
|
||||||
|
|
||||||
|
* Mon Aug 31 2026 Cristian Cezar Moisés <sac@securityops.co> - 5.2.4-1
|
||||||
|
- Make the static Windows GUI package-version check robust to canonical CRLF
|
||||||
|
checkouts, advance source-only package metadata, and prepare final hashes.
|
||||||
|
|
||||||
* Mon Aug 31 2026 Cristian Cezar Moisés <sac@securityops.co> - 5.2.3-1
|
* Mon Aug 31 2026 Cristian Cezar Moisés <sac@securityops.co> - 5.2.3-1
|
||||||
- Correct the release-package CI version checks and portable GUI version
|
- Correct the release-package CI version checks and portable GUI version
|
||||||
contract, and make the openSUSE container replace busybox-gawk before
|
contract, and make the openSUSE container replace busybox-gawk before
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,9 @@ HAVE_EXTERNAL_TARGET=0
|
||||||
declare -a TAGS=()
|
declare -a TAGS=()
|
||||||
declare -a ARCHIVES=()
|
declare -a ARCHIVES=()
|
||||||
declare -a TREES=()
|
declare -a TREES=()
|
||||||
|
TAG_COUNT=0
|
||||||
|
ARCHIVE_COUNT=0
|
||||||
|
TREE_COUNT=0
|
||||||
|
|
||||||
FAILURES=0
|
FAILURES=0
|
||||||
SCANNED=0
|
SCANNED=0
|
||||||
|
|
@ -92,17 +95,20 @@ while (($#)); do
|
||||||
--tag)
|
--tag)
|
||||||
(($# >= 2)) || { printf 'ERROR: --tag requires a revision\n' >&2; exit 2; }
|
(($# >= 2)) || { printf 'ERROR: --tag requires a revision\n' >&2; exit 2; }
|
||||||
TAGS+=("$2")
|
TAGS+=("$2")
|
||||||
|
TAG_COUNT=$((TAG_COUNT + 1))
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
--archive)
|
--archive)
|
||||||
(($# >= 2)) || { printf 'ERROR: --archive requires a file\n' >&2; exit 2; }
|
(($# >= 2)) || { printf 'ERROR: --archive requires a file\n' >&2; exit 2; }
|
||||||
ARCHIVES+=("$2")
|
ARCHIVES+=("$2")
|
||||||
|
ARCHIVE_COUNT=$((ARCHIVE_COUNT + 1))
|
||||||
HAVE_EXTERNAL_TARGET=1
|
HAVE_EXTERNAL_TARGET=1
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
--tree)
|
--tree)
|
||||||
(($# >= 2)) || { printf 'ERROR: --tree requires a directory\n' >&2; exit 2; }
|
(($# >= 2)) || { printf 'ERROR: --tree requires a directory\n' >&2; exit 2; }
|
||||||
TREES+=("$2")
|
TREES+=("$2")
|
||||||
|
TREE_COUNT=$((TREE_COUNT + 1))
|
||||||
HAVE_EXTERNAL_TARGET=1
|
HAVE_EXTERNAL_TARGET=1
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
|
|
@ -127,7 +133,7 @@ while (($#)); do
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
if ((HAVE_EXTERNAL_TARGET)) && ((ROOT_REQUESTED == 0)) && ((${#TAGS[@]} == 0)); then
|
if ((HAVE_EXTERNAL_TARGET)) && ((ROOT_REQUESTED == 0)) && ((TAG_COUNT == 0)); then
|
||||||
REPOSITORY_AUDIT=0
|
REPOSITORY_AUDIT=0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -159,6 +165,10 @@ safe_path_for_output() {
|
||||||
for ((index = 0; index < length; index++)); do
|
for ((index = 0; index < length; index++)); do
|
||||||
character=${path:index:1}
|
character=${path:index:1}
|
||||||
printf -v byte '%d' "'$character"
|
printf -v byte '%d' "'$character"
|
||||||
|
# Bash 3.2 can sign-extend bytes >= 0x80 when converting a character
|
||||||
|
# with %d. Normalize to an unsigned octet before UTF-8 validation and
|
||||||
|
# diagnostic escaping.
|
||||||
|
byte=$((byte & 0xff))
|
||||||
|
|
||||||
if ((byte < 0x20 || byte == 0x7f)); then
|
if ((byte < 0x20 || byte == 0x7f)); then
|
||||||
printf -v escaped '\\x%02x' "$byte"
|
printf -v escaped '\\x%02x' "$byte"
|
||||||
|
|
@ -179,6 +189,7 @@ safe_path_for_output() {
|
||||||
if ((byte >= 0xc2 && byte <= 0xdf && index + 1 < length)); then
|
if ((byte >= 0xc2 && byte <= 0xdf && index + 1 < length)); then
|
||||||
character=${path:index+1:1}
|
character=${path:index+1:1}
|
||||||
printf -v byte2 '%d' "'$character"
|
printf -v byte2 '%d' "'$character"
|
||||||
|
byte2=$((byte2 & 0xff))
|
||||||
if ((byte2 >= 0x80 && byte2 <= 0xbf)); then
|
if ((byte2 >= 0x80 && byte2 <= 0xbf)); then
|
||||||
codepoint=$(((byte & 0x1f) << 6 | (byte2 & 0x3f)))
|
codepoint=$(((byte & 0x1f) << 6 | (byte2 & 0x3f)))
|
||||||
sequence=${path:index:2}
|
sequence=${path:index:2}
|
||||||
|
|
@ -186,8 +197,10 @@ safe_path_for_output() {
|
||||||
elif ((byte >= 0xe0 && byte <= 0xef && index + 2 < length)); then
|
elif ((byte >= 0xe0 && byte <= 0xef && index + 2 < length)); then
|
||||||
character=${path:index+1:1}
|
character=${path:index+1:1}
|
||||||
printf -v byte2 '%d' "'$character"
|
printf -v byte2 '%d' "'$character"
|
||||||
|
byte2=$((byte2 & 0xff))
|
||||||
character=${path:index+2:1}
|
character=${path:index+2:1}
|
||||||
printf -v byte3 '%d' "'$character"
|
printf -v byte3 '%d' "'$character"
|
||||||
|
byte3=$((byte3 & 0xff))
|
||||||
if ((byte3 >= 0x80 && byte3 <= 0xbf &&
|
if ((byte3 >= 0x80 && byte3 <= 0xbf &&
|
||||||
((byte == 0xe0 && byte2 >= 0xa0 && byte2 <= 0xbf) ||
|
((byte == 0xe0 && byte2 >= 0xa0 && byte2 <= 0xbf) ||
|
||||||
(byte >= 0xe1 && byte <= 0xec && byte2 >= 0x80 && byte2 <= 0xbf) ||
|
(byte >= 0xe1 && byte <= 0xec && byte2 >= 0x80 && byte2 <= 0xbf) ||
|
||||||
|
|
@ -200,10 +213,13 @@ safe_path_for_output() {
|
||||||
elif ((byte >= 0xf0 && byte <= 0xf4 && index + 3 < length)); then
|
elif ((byte >= 0xf0 && byte <= 0xf4 && index + 3 < length)); then
|
||||||
character=${path:index+1:1}
|
character=${path:index+1:1}
|
||||||
printf -v byte2 '%d' "'$character"
|
printf -v byte2 '%d' "'$character"
|
||||||
|
byte2=$((byte2 & 0xff))
|
||||||
character=${path:index+2:1}
|
character=${path:index+2:1}
|
||||||
printf -v byte3 '%d' "'$character"
|
printf -v byte3 '%d' "'$character"
|
||||||
|
byte3=$((byte3 & 0xff))
|
||||||
character=${path:index+3:1}
|
character=${path:index+3:1}
|
||||||
printf -v byte4 '%d' "'$character"
|
printf -v byte4 '%d' "'$character"
|
||||||
|
byte4=$((byte4 & 0xff))
|
||||||
if ((byte3 >= 0x80 && byte3 <= 0xbf &&
|
if ((byte3 >= 0x80 && byte3 <= 0xbf &&
|
||||||
byte4 >= 0x80 && byte4 <= 0xbf &&
|
byte4 >= 0x80 && byte4 <= 0xbf &&
|
||||||
((byte == 0xf0 && byte2 >= 0x90 && byte2 <= 0xbf) ||
|
((byte == 0xf0 && byte2 >= 0x90 && byte2 <= 0xbf) ||
|
||||||
|
|
@ -268,7 +284,9 @@ path_stays_below_root() {
|
||||||
[[ $candidate != /* && $candidate != //* ]] || return 1
|
[[ $candidate != /* && $candidate != //* ]] || return 1
|
||||||
[[ ! $candidate =~ ^[[:alpha:]]: ]] || return 1
|
[[ ! $candidate =~ ^[[:alpha:]]: ]] || return 1
|
||||||
read -r -a components <<< "$candidate"
|
read -r -a components <<< "$candidate"
|
||||||
for component in "${components[@]}"; do
|
# Bash 3.2 treats an empty array expansion as unset under `set -u`.
|
||||||
|
# The + guard expands to no words for an empty path component list.
|
||||||
|
for component in ${components[@]+"${components[@]}"}; do
|
||||||
case $component in
|
case $component in
|
||||||
''|.) ;;
|
''|.) ;;
|
||||||
..)
|
..)
|
||||||
|
|
@ -821,38 +839,44 @@ if ((REPOSITORY_AUDIT)); then
|
||||||
else
|
else
|
||||||
fail_path git-archive-HEAD HEAD 'repository has no commit'
|
fail_path git-archive-HEAD HEAD 'repository has no commit'
|
||||||
fi
|
fi
|
||||||
for target in "${TAGS[@]}"; do
|
if ((TAG_COUNT)); then
|
||||||
if git -C "$ROOT" rev-parse --verify -q "$target^{commit}" >/dev/null; then
|
for target in "${TAGS[@]}"; do
|
||||||
scan_git_archive "$ROOT" "$target" "git-archive-$target"
|
if git -C "$ROOT" rev-parse --verify -q "$target^{commit}" >/dev/null; then
|
||||||
|
scan_git_archive "$ROOT" "$target" "git-archive-$target"
|
||||||
|
else
|
||||||
|
fail_path git-tag "$target" 'revision does not resolve to a commit'
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ((TREE_COUNT)); then
|
||||||
|
for target in "${TREES[@]}"; do
|
||||||
|
if [[ -d $target ]]; then
|
||||||
|
target=$(canonicalize_allow_missing "$target") || {
|
||||||
|
fail_path standalone-tree "$target" 'cannot canonicalize tree'
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
scan_tree "$target" standalone-tree 0
|
||||||
else
|
else
|
||||||
fail_path git-tag "$target" 'revision does not resolve to a commit'
|
fail_path standalone-tree "$target" 'tree does not exist'
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for target in "${TREES[@]}"; do
|
if ((ARCHIVE_COUNT)); then
|
||||||
if [[ -d $target ]]; then
|
for target in "${ARCHIVES[@]}"; do
|
||||||
target=$(canonicalize_allow_missing "$target") || {
|
if [[ -f $target ]]; then
|
||||||
fail_path standalone-tree "$target" 'cannot canonicalize tree'
|
target=$(canonicalize_allow_missing "$target") || {
|
||||||
continue
|
fail_path standalone-archive "$target" 'cannot canonicalize archive'
|
||||||
}
|
continue
|
||||||
scan_tree "$target" standalone-tree 0
|
}
|
||||||
else
|
scan_archive "$target" "${target##*/}" standalone-archive 0
|
||||||
fail_path standalone-tree "$target" 'tree does not exist'
|
else
|
||||||
fi
|
fail_path standalone-archive "$target" 'archive does not exist'
|
||||||
done
|
fi
|
||||||
|
done
|
||||||
for target in "${ARCHIVES[@]}"; do
|
fi
|
||||||
if [[ -f $target ]]; then
|
|
||||||
target=$(canonicalize_allow_missing "$target") || {
|
|
||||||
fail_path standalone-archive "$target" 'cannot canonicalize archive'
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
scan_archive "$target" "${target##*/}" standalone-archive 0
|
|
||||||
else
|
|
||||||
fail_path standalone-archive "$target" 'archive does not exist'
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if ((FAILURES == 0)); then
|
if ((FAILURES == 0)); then
|
||||||
printf 'PASS source-only: %d files, %d archives\n' "$SCANNED" "$ARCHIVES_SCANNED"
|
printf 'PASS source-only: %d files, %d archives\n' "$SCANNED" "$ARCHIVES_SCANNED"
|
||||||
|
|
|
||||||
|
|
@ -556,20 +556,46 @@ void zuptsdk_keypair_destroy(zuptsdk_keypair_t *kp) {
|
||||||
static int zsdk_copy_file(const char *src, const char *dst, mode_t mode) {
|
static int zsdk_copy_file(const char *src, const char *dst, mode_t mode) {
|
||||||
FILE *fi = fopen(src, "rb");
|
FILE *fi = fopen(src, "rb");
|
||||||
if (!fi) return ZSDK_FAIL(ZUPTSDK_ERR_IO, "open %s", src);
|
if (!fi) return ZSDK_FAIL(ZUPTSDK_ERR_IO, "open %s", src);
|
||||||
FILE *fo = fopen(dst, "wb");
|
FILE *fo = NULL;
|
||||||
if (!fo) { fclose(fi); return ZSDK_FAIL(ZUPTSDK_ERR_IO, "create %s", dst); }
|
zupt_atomic_output_t *output = zupt_atomic_output_open(dst, &fo);
|
||||||
uint8_t buf[4096];
|
if (!output) {
|
||||||
size_t n;
|
int saved_errno = errno;
|
||||||
int rc = ZUPTSDK_OK;
|
fclose(fi);
|
||||||
while ((n = fread(buf, 1, sizeof(buf), fi)) > 0)
|
errno = saved_errno;
|
||||||
if (fwrite(buf, 1, n, fo) != n) { rc = ZSDK_FAIL(ZUPTSDK_ERR_IO, "write %s", dst); break; }
|
return ZSDK_FAIL(ZUPTSDK_ERR_IO, "create %s", dst);
|
||||||
zuptsdk_secure_zero(buf, sizeof(buf));
|
}
|
||||||
fclose(fi); fclose(fo);
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
if (rc == ZUPTSDK_OK) chmod(dst, mode);
|
/* Apply permissions to the private temporary object, never to a
|
||||||
|
* re-resolved destination path. */
|
||||||
|
if (fchmod(fileno(fo), mode) != 0) {
|
||||||
|
int saved_errno = errno;
|
||||||
|
fclose(fi);
|
||||||
|
(void)zupt_atomic_output_finish(output, 0);
|
||||||
|
errno = saved_errno;
|
||||||
|
return ZSDK_FAIL(ZUPTSDK_ERR_IO, "set permissions on %s", dst);
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
(void)mode;
|
(void)mode;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
uint8_t buf[4096];
|
||||||
|
size_t n;
|
||||||
|
int rc = ZUPTSDK_OK;
|
||||||
|
while ((n = fread(buf, 1, sizeof(buf), fi)) > 0) {
|
||||||
|
if (fwrite(buf, 1, n, fo) != n) {
|
||||||
|
rc = ZSDK_FAIL(ZUPTSDK_ERR_IO, "write %s", dst);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rc == ZUPTSDK_OK && ferror(fi))
|
||||||
|
rc = ZSDK_FAIL(ZUPTSDK_ERR_IO, "read %s", src);
|
||||||
|
zuptsdk_secure_zero(buf, sizeof(buf));
|
||||||
|
if (fclose(fi) != 0 && rc == ZUPTSDK_OK)
|
||||||
|
rc = ZSDK_FAIL(ZUPTSDK_ERR_IO, "close %s", src);
|
||||||
|
if (zupt_atomic_output_finish(output, rc == ZUPTSDK_OK) != 0 &&
|
||||||
|
rc == ZUPTSDK_OK)
|
||||||
|
rc = ZSDK_FAIL(ZUPTSDK_ERR_IO, "publish %s", dst);
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,10 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#ifndef _WIN32
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#endif
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
static int g_pass = 0, g_fail = 0;
|
static int g_pass = 0, g_fail = 0;
|
||||||
|
|
@ -47,6 +51,98 @@ static const uint8_t TEST_DATA[] =
|
||||||
"Lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod. "
|
"Lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod. "
|
||||||
"End of test data.\n";
|
"End of test data.\n";
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
static int file_matches(const char *path, const void *expected,
|
||||||
|
size_t expected_size) {
|
||||||
|
struct stat info;
|
||||||
|
char observed[128];
|
||||||
|
if (expected_size > sizeof(observed))
|
||||||
|
return 0;
|
||||||
|
int fd = open(path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC | O_NONBLOCK);
|
||||||
|
if (fd < 0) return 0;
|
||||||
|
int ok = fstat(fd, &info) == 0 && S_ISREG(info.st_mode) &&
|
||||||
|
info.st_size >= 0 &&
|
||||||
|
(uint64_t)info.st_size == (uint64_t)expected_size;
|
||||||
|
size_t got = 0;
|
||||||
|
while (ok && got < expected_size) {
|
||||||
|
ssize_t count = read(fd, observed + got, expected_size - got);
|
||||||
|
if (count <= 0) {
|
||||||
|
ok = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
got += (size_t)count;
|
||||||
|
}
|
||||||
|
if (close(fd) != 0) ok = 0;
|
||||||
|
return ok && got == expected_size &&
|
||||||
|
memcmp(observed, expected, expected_size) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int regular_file_info(const char *path, struct stat *info) {
|
||||||
|
int fd = open(path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC | O_NONBLOCK);
|
||||||
|
if (fd < 0) return 0;
|
||||||
|
int ok = fstat(fd, info) == 0 && S_ISREG(info->st_mode);
|
||||||
|
if (close(fd) != 0) ok = 0;
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int private_key_save_avoids_link_targets(const zuptsdk_keypair_t *kp) {
|
||||||
|
static const char sentinel[] = "do not replace through a symlink\n";
|
||||||
|
char workspace[] = "/tmp/zupt-sdk-link-save.XXXXXX";
|
||||||
|
char target[192];
|
||||||
|
char symlink_path[192];
|
||||||
|
char hardlink_path[192];
|
||||||
|
FILE *stream;
|
||||||
|
struct stat target_st;
|
||||||
|
struct stat output_st;
|
||||||
|
int ok = 0;
|
||||||
|
|
||||||
|
if (!mkdtemp(workspace)) return 0;
|
||||||
|
snprintf(target, sizeof(target), "%s/target", workspace);
|
||||||
|
snprintf(symlink_path, sizeof(symlink_path), "%s/symlink-output",
|
||||||
|
workspace);
|
||||||
|
snprintf(hardlink_path, sizeof(hardlink_path), "%s/hardlink-output",
|
||||||
|
workspace);
|
||||||
|
|
||||||
|
stream = fopen(target, "wb");
|
||||||
|
if (!stream) goto cleanup;
|
||||||
|
size_t written = fwrite(sentinel, 1, sizeof(sentinel) - 1, stream);
|
||||||
|
int close_rc = fclose(stream);
|
||||||
|
if (written != sizeof(sentinel) - 1 || close_rc != 0)
|
||||||
|
goto cleanup;
|
||||||
|
|
||||||
|
if (symlink(target, symlink_path) != 0 ||
|
||||||
|
zuptsdk_keypair_save_private(kp, symlink_path) != ZUPTSDK_OK ||
|
||||||
|
!file_matches(target, sentinel, sizeof(sentinel) - 1) ||
|
||||||
|
!regular_file_info(target, &target_st) ||
|
||||||
|
!regular_file_info(symlink_path, &output_st) ||
|
||||||
|
(target_st.st_dev == output_st.st_dev &&
|
||||||
|
target_st.st_ino == output_st.st_ino) ||
|
||||||
|
output_st.st_size <= 0 ||
|
||||||
|
(output_st.st_mode & 0777) != 0600)
|
||||||
|
goto cleanup;
|
||||||
|
|
||||||
|
if (link(target, hardlink_path) != 0 ||
|
||||||
|
zuptsdk_keypair_save_private(kp, hardlink_path) != ZUPTSDK_OK ||
|
||||||
|
!file_matches(target, sentinel, sizeof(sentinel) - 1) ||
|
||||||
|
!regular_file_info(target, &target_st) ||
|
||||||
|
!regular_file_info(hardlink_path, &output_st) ||
|
||||||
|
(target_st.st_dev == output_st.st_dev &&
|
||||||
|
target_st.st_ino == output_st.st_ino) ||
|
||||||
|
output_st.st_size <= 0 ||
|
||||||
|
(output_st.st_mode & 0777) != 0600)
|
||||||
|
goto cleanup;
|
||||||
|
|
||||||
|
ok = 1;
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
unlink(symlink_path);
|
||||||
|
unlink(hardlink_path);
|
||||||
|
unlink(target);
|
||||||
|
rmdir(workspace);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static void test_version(void) {
|
static void test_version(void) {
|
||||||
TEST("version_string returns non-NULL");
|
TEST("version_string returns non-NULL");
|
||||||
const char *v = zuptsdk_version_string();
|
const char *v = zuptsdk_version_string();
|
||||||
|
|
@ -250,6 +346,19 @@ cleanup:
|
||||||
|
|
||||||
static void test_keypair_pq(void) {
|
static void test_keypair_pq(void) {
|
||||||
TEST("keypair_generate + compress_pq + extract_pq");
|
TEST("keypair_generate + compress_pq + extract_pq");
|
||||||
|
char saved_priv[160];
|
||||||
|
char saved_pub[160];
|
||||||
|
#ifdef _WIN32
|
||||||
|
snprintf(saved_priv, sizeof(saved_priv), "/tmp/_zsdk_priv_%ld.key",
|
||||||
|
(long)getpid());
|
||||||
|
snprintf(saved_pub, sizeof(saved_pub), "/tmp/_zsdk_pub_%ld.key",
|
||||||
|
(long)getpid());
|
||||||
|
unlink(saved_priv);
|
||||||
|
unlink(saved_pub);
|
||||||
|
#else
|
||||||
|
char saved_workspace[] = "/tmp/zupt-sdk-roundtrip.XXXXXX";
|
||||||
|
#endif
|
||||||
|
|
||||||
zuptsdk_ctx_t *ctx = NULL;
|
zuptsdk_ctx_t *ctx = NULL;
|
||||||
CHECK(zuptsdk_ctx_create(&ctx), "ctx");
|
CHECK(zuptsdk_ctx_create(&ctx), "ctx");
|
||||||
|
|
||||||
|
|
@ -257,17 +366,45 @@ static void test_keypair_pq(void) {
|
||||||
int rc = zuptsdk_keypair_generate(ctx, &kp);
|
int rc = zuptsdk_keypair_generate(ctx, &kp);
|
||||||
if (rc != ZUPTSDK_OK) { FAIL("keygen"); zuptsdk_ctx_destroy(ctx); return; }
|
if (rc != ZUPTSDK_OK) { FAIL("keygen"); zuptsdk_ctx_destroy(ctx); return; }
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
if (!mkdtemp(saved_workspace)) {
|
||||||
|
FAIL("private temporary workspace");
|
||||||
|
zuptsdk_keypair_destroy(kp);
|
||||||
|
zuptsdk_ctx_destroy(ctx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
snprintf(saved_priv, sizeof(saved_priv), "%s/private.key",
|
||||||
|
saved_workspace);
|
||||||
|
snprintf(saved_pub, sizeof(saved_pub), "%s/public.key",
|
||||||
|
saved_workspace);
|
||||||
|
if (!private_key_save_avoids_link_targets(kp)) {
|
||||||
|
FAIL("private key save followed a symlink or hardlink target");
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Save and load to exercise that path too */
|
/* Save and load to exercise that path too */
|
||||||
rc = zuptsdk_keypair_save_private(kp, "/tmp/_zsdk_priv.key");
|
rc = zuptsdk_keypair_save_private(kp, saved_priv);
|
||||||
if (rc != ZUPTSDK_OK) { FAIL("save priv"); goto err; }
|
if (rc != ZUPTSDK_OK) { FAIL("save priv"); goto err; }
|
||||||
rc = zuptsdk_keypair_save_public(kp, "/tmp/_zsdk_pub.key");
|
rc = zuptsdk_keypair_save_public(kp, saved_pub);
|
||||||
if (rc != ZUPTSDK_OK) { FAIL("save pub"); goto err; }
|
if (rc != ZUPTSDK_OK) { FAIL("save pub"); goto err; }
|
||||||
|
#ifndef _WIN32
|
||||||
|
struct stat private_st;
|
||||||
|
struct stat public_st;
|
||||||
|
if (!regular_file_info(saved_priv, &private_st) ||
|
||||||
|
!regular_file_info(saved_pub, &public_st) ||
|
||||||
|
(private_st.st_mode & 0777) != 0600 ||
|
||||||
|
(public_st.st_mode & 0777) != 0644) {
|
||||||
|
FAIL("saved key permissions do not match the requested modes");
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
zuptsdk_pubkey_t *pub = NULL;
|
zuptsdk_pubkey_t *pub = NULL;
|
||||||
zuptsdk_privkey_t *priv = NULL;
|
zuptsdk_privkey_t *priv = NULL;
|
||||||
rc = zuptsdk_pubkey_load("/tmp/_zsdk_pub.key", &pub);
|
rc = zuptsdk_pubkey_load(saved_pub, &pub);
|
||||||
if (rc != ZUPTSDK_OK) { FAIL("load pub"); goto err; }
|
if (rc != ZUPTSDK_OK) { FAIL("load pub"); goto err; }
|
||||||
rc = zuptsdk_privkey_load("/tmp/_zsdk_priv.key", &priv);
|
rc = zuptsdk_privkey_load(saved_priv, &priv);
|
||||||
if (rc != ZUPTSDK_OK) { FAIL("load priv"); zuptsdk_pubkey_destroy(pub); goto err; }
|
if (rc != ZUPTSDK_OK) { FAIL("load priv"); zuptsdk_pubkey_destroy(pub); goto err; }
|
||||||
|
|
||||||
zuptsdk_options_t *opts = NULL;
|
zuptsdk_options_t *opts = NULL;
|
||||||
|
|
@ -295,8 +432,10 @@ static void test_keypair_pq(void) {
|
||||||
zuptsdk_privkey_destroy(priv);
|
zuptsdk_privkey_destroy(priv);
|
||||||
zuptsdk_options_destroy(opts);
|
zuptsdk_options_destroy(opts);
|
||||||
|
|
||||||
unlink("/tmp/_zsdk_priv.key");
|
if (unlink(saved_priv) != 0 || unlink(saved_pub) != 0) ok = 0;
|
||||||
unlink("/tmp/_zsdk_pub.key");
|
#ifndef _WIN32
|
||||||
|
if (rmdir(saved_workspace) != 0) ok = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!ok) { FAIL("byte mismatch or rc != OK"); zuptsdk_keypair_destroy(kp); zuptsdk_ctx_destroy(ctx); return; }
|
if (!ok) { FAIL("byte mismatch or rc != OK"); zuptsdk_keypair_destroy(kp); zuptsdk_ctx_destroy(ctx); return; }
|
||||||
zuptsdk_keypair_destroy(kp);
|
zuptsdk_keypair_destroy(kp);
|
||||||
|
|
@ -305,6 +444,11 @@ static void test_keypair_pq(void) {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
err:
|
err:
|
||||||
|
unlink(saved_priv);
|
||||||
|
unlink(saved_pub);
|
||||||
|
#ifndef _WIN32
|
||||||
|
rmdir(saved_workspace);
|
||||||
|
#endif
|
||||||
zuptsdk_keypair_destroy(kp);
|
zuptsdk_keypair_destroy(kp);
|
||||||
zuptsdk_ctx_destroy(ctx);
|
zuptsdk_ctx_destroy(ctx);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,14 +41,15 @@
|
||||||
*
|
*
|
||||||
* Implementation strategy:
|
* Implementation strategy:
|
||||||
* - Prefer `explicit_bzero` (BSD/glibc 2.25+, guaranteed-secure)
|
* - Prefer `explicit_bzero` (BSD/glibc 2.25+, guaranteed-secure)
|
||||||
* - Fall back to `memset_explicit` (C23)
|
* - Otherwise use a volatile-pointer loop (compiler cannot
|
||||||
* - Last resort: volatile-pointer memset (compiler cannot
|
|
||||||
* prove the writes are dead)
|
* prove the writes are dead)
|
||||||
* ═══════════════════════════════════════════════════════════════ */
|
* ═══════════════════════════════════════════════════════════════ */
|
||||||
|
|
||||||
#if defined(__GLIBC__) && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 25))
|
#if defined(__GLIBC__) && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 25))
|
||||||
# define VV_HAS_EXPLICIT_BZERO 1
|
# define VV_HAS_EXPLICIT_BZERO 1
|
||||||
#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
|
/* Darwin intentionally uses the volatile fallback: current deployment targets
|
||||||
|
* do not guarantee an explicit_bzero symbol in libSystem. */
|
||||||
|
#elif defined(__FreeBSD__) || defined(__OpenBSD__)
|
||||||
# define VV_HAS_EXPLICIT_BZERO 1
|
# define VV_HAS_EXPLICIT_BZERO 1
|
||||||
#else
|
#else
|
||||||
# define VV_HAS_EXPLICIT_BZERO 0
|
# define VV_HAS_EXPLICIT_BZERO 0
|
||||||
|
|
|
||||||
123
src/zupt_disk.c
123
src/zupt_disk.c
|
|
@ -1220,23 +1220,35 @@ zupt_error_t zupt_disk_restore(const char *archive_path, const char *target_path
|
||||||
#else
|
#else
|
||||||
int tgt_fd = -1;
|
int tgt_fd = -1;
|
||||||
int is_block_dev = 0;
|
int is_block_dev = 0;
|
||||||
struct stat target_st;
|
/* Resolve the target exactly once before making any type or identity
|
||||||
|
* decision. The open is non-truncating, O_NOFOLLOW rejects a final
|
||||||
if (lstat(target_path, &target_st) == 0) {
|
* symlink, and fstat classifies the kernel object that was actually
|
||||||
if (S_ISLNK(target_st.st_mode)) {
|
* opened. Device restores retain this same descriptor through the final
|
||||||
fprintf(stderr, "Error: refusing a symbolic-link restore target.\n");
|
* write, so a concurrent pathname exchange cannot redirect the restore. */
|
||||||
fclose(f);
|
tgt_fd = open(target_path, O_WRONLY | O_NOFOLLOW | O_CLOEXEC |
|
||||||
return ZUPT_ERR_INVALID;
|
O_NONBLOCK | O_SYNC);
|
||||||
}
|
if (tgt_fd >= 0) {
|
||||||
if (S_ISREG(target_st.st_mode)) {
|
struct stat opened_st;
|
||||||
if (target_st.st_dev == archive_identity.device &&
|
if (fstat(tgt_fd, &opened_st) != 0) {
|
||||||
target_st.st_ino == archive_identity.inode) {
|
int saved_errno = errno;
|
||||||
|
close(tgt_fd);
|
||||||
|
tgt_fd = -1;
|
||||||
|
errno = saved_errno;
|
||||||
|
} else if (S_ISREG(opened_st.st_mode)) {
|
||||||
|
int close_result = close(tgt_fd);
|
||||||
|
tgt_fd = -1;
|
||||||
|
if (close_result != 0) {
|
||||||
|
fclose(f);
|
||||||
|
return ZUPT_ERR_IO;
|
||||||
|
}
|
||||||
|
if (opened_st.st_dev == archive_identity.device &&
|
||||||
|
opened_st.st_ino == archive_identity.inode) {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"Error: archive and restore target are the same file.\n");
|
"Error: archive and restore target are the same file.\n");
|
||||||
fclose(f);
|
fclose(f);
|
||||||
return ZUPT_ERR_INVALID;
|
return ZUPT_ERR_INVALID;
|
||||||
}
|
}
|
||||||
if (target_st.st_nlink != 1) {
|
if (opened_st.st_nlink != 1) {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"Error: refusing a multiply-linked restore target.\n");
|
"Error: refusing a multiply-linked restore target.\n");
|
||||||
fclose(f);
|
fclose(f);
|
||||||
|
|
@ -1244,59 +1256,46 @@ zupt_error_t zupt_disk_restore(const char *archive_path, const char *target_path
|
||||||
}
|
}
|
||||||
target_atomic =
|
target_atomic =
|
||||||
zupt_atomic_output_open(target_path, &target_stream);
|
zupt_atomic_output_open(target_path, &target_stream);
|
||||||
} else if (S_ISBLK(target_st.st_mode) ||
|
} else if (S_ISBLK(opened_st.st_mode) ||
|
||||||
S_ISCHR(target_st.st_mode)) {
|
S_ISCHR(opened_st.st_mode)) {
|
||||||
tgt_fd = open(target_path, O_WRONLY | O_NOFOLLOW | O_CLOEXEC |
|
int flags = fcntl(tgt_fd, F_GETFL);
|
||||||
O_NONBLOCK | O_SYNC);
|
if (flags < 0 ||
|
||||||
if (tgt_fd >= 0) {
|
fcntl(tgt_fd, F_SETFL, flags & ~O_NONBLOCK) != 0) {
|
||||||
struct stat opened_st;
|
close(tgt_fd);
|
||||||
if (fstat(tgt_fd, &opened_st) != 0 ||
|
tgt_fd = -1;
|
||||||
opened_st.st_dev != target_st.st_dev ||
|
} else {
|
||||||
opened_st.st_ino != target_st.st_ino ||
|
#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
|
||||||
!(S_ISBLK(opened_st.st_mode) ||
|
uint64_t target_capacity = 0;
|
||||||
S_ISCHR(opened_st.st_mode))) {
|
if (!disk_restore_target_capacity(
|
||||||
|
tgt_fd, &opened_st, &target_capacity)) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"Error: cannot determine restore device "
|
||||||
|
"capacity safely.\n");
|
||||||
close(tgt_fd);
|
close(tgt_fd);
|
||||||
tgt_fd = -1;
|
tgt_fd = -1;
|
||||||
errno = EAGAIN;
|
} else if (expected_size > target_capacity) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"Error: disk image (%llu bytes) exceeds "
|
||||||
|
"restore device capacity (%llu bytes).\n",
|
||||||
|
(unsigned long long)expected_size,
|
||||||
|
(unsigned long long)target_capacity);
|
||||||
|
close(tgt_fd);
|
||||||
|
tgt_fd = -1;
|
||||||
|
errno = EFBIG;
|
||||||
} else {
|
} else {
|
||||||
int flags = fcntl(tgt_fd, F_GETFL);
|
is_block_dev = 1;
|
||||||
if (flags < 0 ||
|
|
||||||
fcntl(tgt_fd, F_SETFL, flags & ~O_NONBLOCK) != 0) {
|
|
||||||
close(tgt_fd);
|
|
||||||
tgt_fd = -1;
|
|
||||||
} else {
|
|
||||||
#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
|
|
||||||
uint64_t target_capacity = 0;
|
|
||||||
if (!disk_restore_target_capacity(
|
|
||||||
tgt_fd, &opened_st, &target_capacity)) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"Error: cannot determine restore device "
|
|
||||||
"capacity safely.\n");
|
|
||||||
close(tgt_fd);
|
|
||||||
tgt_fd = -1;
|
|
||||||
} else if (expected_size > target_capacity) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"Error: disk image (%llu bytes) exceeds "
|
|
||||||
"restore device capacity (%llu bytes).\n",
|
|
||||||
(unsigned long long)expected_size,
|
|
||||||
(unsigned long long)target_capacity);
|
|
||||||
close(tgt_fd);
|
|
||||||
tgt_fd = -1;
|
|
||||||
errno = EFBIG;
|
|
||||||
} else {
|
|
||||||
is_block_dev = 1;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
fprintf(stderr,
|
|
||||||
"Error: restore-device capacity queries are "
|
|
||||||
"not supported on this platform.\n");
|
|
||||||
close(tgt_fd);
|
|
||||||
tgt_fd = -1;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
fprintf(stderr,
|
||||||
|
"Error: restore-device capacity queries are "
|
||||||
|
"not supported on this platform.\n");
|
||||||
|
close(tgt_fd);
|
||||||
|
tgt_fd = -1;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
close(tgt_fd);
|
||||||
|
tgt_fd = -1;
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"Error: restore target is not a regular file or device.\n");
|
"Error: restore target is not a regular file or device.\n");
|
||||||
fclose(f);
|
fclose(f);
|
||||||
|
|
@ -1304,8 +1303,12 @@ zupt_error_t zupt_disk_restore(const char *archive_path, const char *target_path
|
||||||
}
|
}
|
||||||
} else if (errno == ENOENT) {
|
} else if (errno == ENOENT) {
|
||||||
target_atomic = zupt_atomic_output_open(target_path, &target_stream);
|
target_atomic = zupt_atomic_output_open(target_path, &target_stream);
|
||||||
|
} else if (errno == ELOOP) {
|
||||||
|
fprintf(stderr, "Error: refusing a symbolic-link restore target.\n");
|
||||||
|
fclose(f);
|
||||||
|
return ZUPT_ERR_INVALID;
|
||||||
} else {
|
} else {
|
||||||
fprintf(stderr, "Error: Cannot inspect target '%s': %s\n",
|
fprintf(stderr, "Error: Cannot open target '%s': %s\n",
|
||||||
target_path, strerror(errno));
|
target_path, strerror(errno));
|
||||||
fclose(f);
|
fclose(f);
|
||||||
return ZUPT_ERR_IO;
|
return ZUPT_ERR_IO;
|
||||||
|
|
|
||||||
|
|
@ -4221,7 +4221,7 @@ static zupt_error_t open_archive(FILE *f, zupt_options_t *opts,
|
||||||
* parser ignored block_type at this position and decoded whatever it
|
* parser ignored block_type at this position and decoded whatever it
|
||||||
* found — making the byte truly unauthenticated. Now it is structurally
|
* found — making the byte truly unauthenticated. Now it is structurally
|
||||||
* validated (rejected at parse time on mismatch), which is the
|
* validated (rejected at parse time on mismatch), which is the
|
||||||
* OPAQUE-class coverage promised by PROMPT.md §5. */
|
* OPAQUE-class structural coverage recorded in the audit history. */
|
||||||
if (ib.block_type != ZUPT_BLOCK_INDEX) {
|
if (ib.block_type != ZUPT_BLOCK_INDEX) {
|
||||||
free(ib.payload);
|
free(ib.payload);
|
||||||
return ZUPT_ERR_CORRUPT;
|
return ZUPT_ERR_CORRUPT;
|
||||||
|
|
|
||||||
397
src/zupt_main.c
397
src/zupt_main.c
|
|
@ -25,7 +25,9 @@
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <conio.h>
|
#include <conio.h>
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
#include <winternl.h>
|
||||||
#else
|
#else
|
||||||
|
#include <fcntl.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <termios.h>
|
#include <termios.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -100,9 +102,11 @@ static int zupt_create_private_temp_directory(char *output, size_t capacity) {
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
#else
|
#else
|
||||||
static const char pattern[] = "/tmp/zupt-bench-XXXXXX";
|
char temp_root[ZUPT_MAX_PATH];
|
||||||
if (sizeof(pattern) > capacity) return 0;
|
if (!realpath("/tmp", temp_root)) return 0;
|
||||||
memcpy(output, pattern, sizeof(pattern));
|
int written = snprintf(output, capacity, "%s/zupt-bench-XXXXXX",
|
||||||
|
temp_root);
|
||||||
|
if (written < 0 || (size_t)written >= capacity) return 0;
|
||||||
if (!mkdtemp(output)) return 0;
|
if (!mkdtemp(output)) return 0;
|
||||||
if (chmod(output, 0700) != 0) {
|
if (chmod(output, 0700) != 0) {
|
||||||
rmdir(output);
|
rmdir(output);
|
||||||
|
|
@ -114,7 +118,97 @@ static int zupt_create_private_temp_directory(char *output, size_t capacity) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
static int zupt_remove_tree_wide(const wchar_t *directory) {
|
static void zupt_win_set_cleanup_errno(NTSTATUS status) {
|
||||||
|
if (status == (NTSTATUS)0xC0000034L || /* STATUS_OBJECT_NAME_NOT_FOUND */
|
||||||
|
status == (NTSTATUS)0xC000003AL) { /* STATUS_OBJECT_PATH_NOT_FOUND */
|
||||||
|
errno = ENOENT;
|
||||||
|
} else {
|
||||||
|
errno = EACCES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Open one entry relative to a pinned parent. Omitting FILE_SHARE_DELETE
|
||||||
|
* keeps the name bound to this handle until cleanup finishes; opening the
|
||||||
|
* reparse point itself prevents a junction or symlink from redirecting the
|
||||||
|
* recursive walk. */
|
||||||
|
static HANDLE zupt_win_open_cleanup_entry(HANDLE parent,
|
||||||
|
const wchar_t *name,
|
||||||
|
int directory_only,
|
||||||
|
int delete_access) {
|
||||||
|
size_t name_length = wcslen(name);
|
||||||
|
if (name_length == 0 ||
|
||||||
|
name_length > (size_t)USHRT_MAX / sizeof(wchar_t)) {
|
||||||
|
errno = ENAMETOOLONG;
|
||||||
|
return INVALID_HANDLE_VALUE;
|
||||||
|
}
|
||||||
|
UNICODE_STRING object_name;
|
||||||
|
object_name.Buffer = (PWSTR)name;
|
||||||
|
object_name.Length = (USHORT)(name_length * sizeof(wchar_t));
|
||||||
|
object_name.MaximumLength = object_name.Length + sizeof(wchar_t);
|
||||||
|
OBJECT_ATTRIBUTES attributes;
|
||||||
|
InitializeObjectAttributes(&attributes, &object_name,
|
||||||
|
OBJ_CASE_INSENSITIVE, parent, NULL);
|
||||||
|
IO_STATUS_BLOCK status_block;
|
||||||
|
HANDLE handle = INVALID_HANDLE_VALUE;
|
||||||
|
ACCESS_MASK access = FILE_LIST_DIRECTORY | FILE_TRAVERSE |
|
||||||
|
FILE_READ_ATTRIBUTES | SYNCHRONIZE;
|
||||||
|
if (delete_access) access |= DELETE;
|
||||||
|
ULONG share = FILE_SHARE_READ | FILE_SHARE_WRITE;
|
||||||
|
if (delete_access) share |= FILE_SHARE_DELETE;
|
||||||
|
ULONG options = FILE_OPEN_REPARSE_POINT | FILE_SYNCHRONOUS_IO_NONALERT;
|
||||||
|
if (directory_only) options |= FILE_DIRECTORY_FILE;
|
||||||
|
NTSTATUS status = NtCreateFile(
|
||||||
|
&handle, access, &attributes, &status_block, NULL,
|
||||||
|
FILE_ATTRIBUTE_NORMAL, share, FILE_OPEN,
|
||||||
|
options, NULL, 0);
|
||||||
|
if (status < 0 || handle == INVALID_HANDLE_VALUE) {
|
||||||
|
zupt_win_set_cleanup_errno(status);
|
||||||
|
return INVALID_HANDLE_VALUE;
|
||||||
|
}
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mark the exact object held by an identity-checked deletion handle. */
|
||||||
|
static int zupt_win_delete_cleanup_handle(HANDLE handle) {
|
||||||
|
FILE_DISPOSITION_INFO disposition;
|
||||||
|
disposition.DeleteFile = TRUE;
|
||||||
|
if (SetFileInformationByHandle(handle, FileDispositionInfo,
|
||||||
|
&disposition, sizeof(disposition)))
|
||||||
|
return 1;
|
||||||
|
errno = EACCES;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reopen an emptied child only after closing its no-delete-sharing traversal
|
||||||
|
* handle. Comparing the filesystem identity before marking the new handle
|
||||||
|
* for deletion makes a close/reopen name exchange fail safely. */
|
||||||
|
static int zupt_win_delete_cleanup_entry(
|
||||||
|
HANDLE parent, const wchar_t *name,
|
||||||
|
const BY_HANDLE_FILE_INFORMATION *expected) {
|
||||||
|
HANDLE handle = zupt_win_open_cleanup_entry(parent, name, 1, 1);
|
||||||
|
if (handle == INVALID_HANDLE_VALUE) return 0;
|
||||||
|
BY_HANDLE_FILE_INFORMATION current;
|
||||||
|
int same = GetFileInformationByHandle(handle, ¤t) &&
|
||||||
|
(current.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0 &&
|
||||||
|
(current.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0 &&
|
||||||
|
current.dwVolumeSerialNumber == expected->dwVolumeSerialNumber &&
|
||||||
|
current.nFileIndexHigh == expected->nFileIndexHigh &&
|
||||||
|
current.nFileIndexLow == expected->nFileIndexLow;
|
||||||
|
int deleted = same && zupt_win_delete_cleanup_handle(handle);
|
||||||
|
int closed = CloseHandle(handle) != 0;
|
||||||
|
if (!same) errno = EBUSY;
|
||||||
|
return deleted && closed;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int zupt_win_plain_directory(HANDLE handle) {
|
||||||
|
BY_HANDLE_FILE_INFORMATION info;
|
||||||
|
return GetFileInformationByHandle(handle, &info) &&
|
||||||
|
(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0 &&
|
||||||
|
(info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int zupt_remove_tree_wide(HANDLE directory_handle,
|
||||||
|
const wchar_t *directory) {
|
||||||
size_t directory_length = wcslen(directory);
|
size_t directory_length = wcslen(directory);
|
||||||
wchar_t *pattern = (wchar_t *)calloc(directory_length + 3u,
|
wchar_t *pattern = (wchar_t *)calloc(directory_length + 3u,
|
||||||
sizeof(*pattern));
|
sizeof(*pattern));
|
||||||
|
|
@ -125,6 +219,8 @@ static int zupt_remove_tree_wide(const wchar_t *directory) {
|
||||||
|
|
||||||
WIN32_FIND_DATAW data;
|
WIN32_FIND_DATAW data;
|
||||||
HANDLE search = FindFirstFileW(pattern, &data);
|
HANDLE search = FindFirstFileW(pattern, &data);
|
||||||
|
DWORD search_error = search == INVALID_HANDLE_VALUE
|
||||||
|
? GetLastError() : ERROR_SUCCESS;
|
||||||
free(pattern);
|
free(pattern);
|
||||||
int failed = 0;
|
int failed = 0;
|
||||||
if (search != INVALID_HANDLE_VALUE) {
|
if (search != INVALID_HANDLE_VALUE) {
|
||||||
|
|
@ -143,23 +239,217 @@ static int zupt_remove_tree_wide(const wchar_t *directory) {
|
||||||
child[directory_length] = L'\\';
|
child[directory_length] = L'\\';
|
||||||
memcpy(child + directory_length + 1u, data.cFileName,
|
memcpy(child + directory_length + 1u, data.cFileName,
|
||||||
(name_length + 1u) * sizeof(*child));
|
(name_length + 1u) * sizeof(*child));
|
||||||
if ((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
|
if (DeleteFileW(child) || RemoveDirectoryW(child)) {
|
||||||
if ((data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
|
free(child);
|
||||||
if (!RemoveDirectoryW(child)) failed = 1;
|
continue;
|
||||||
} else if (zupt_remove_tree_wide(child) != 0) {
|
|
||||||
failed = 1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
SetFileAttributesW(child, FILE_ATTRIBUTE_NORMAL);
|
|
||||||
if (!DeleteFileW(child)) failed = 1;
|
|
||||||
}
|
}
|
||||||
|
DWORD delete_error = GetLastError();
|
||||||
|
if (delete_error == ERROR_FILE_NOT_FOUND ||
|
||||||
|
delete_error == ERROR_PATH_NOT_FOUND) {
|
||||||
|
free(child);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
HANDLE child_handle = zupt_win_open_cleanup_entry(
|
||||||
|
directory_handle, data.cFileName, 1, 0);
|
||||||
|
if (child_handle == INVALID_HANDLE_VALUE) {
|
||||||
|
if (errno != ENOENT) failed = 1;
|
||||||
|
free(child);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int child_failed = 0;
|
||||||
|
BY_HANDLE_FILE_INFORMATION child_identity;
|
||||||
|
if (!GetFileInformationByHandle(child_handle, &child_identity) ||
|
||||||
|
(child_identity.dwFileAttributes &
|
||||||
|
FILE_ATTRIBUTE_DIRECTORY) == 0 ||
|
||||||
|
(child_identity.dwFileAttributes &
|
||||||
|
FILE_ATTRIBUTE_REPARSE_POINT) != 0 ||
|
||||||
|
zupt_remove_tree_wide(child_handle, child) != 0)
|
||||||
|
child_failed = 1;
|
||||||
|
if (!CloseHandle(child_handle)) child_failed = 1;
|
||||||
|
if (!child_failed && !zupt_win_delete_cleanup_entry(
|
||||||
|
directory_handle, data.cFileName, &child_identity))
|
||||||
|
child_failed = 1;
|
||||||
|
if (child_failed) failed = 1;
|
||||||
free(child);
|
free(child);
|
||||||
} while (FindNextFileW(search, &data));
|
} while (FindNextFileW(search, &data));
|
||||||
|
if (GetLastError() != ERROR_NO_MORE_FILES) failed = 1;
|
||||||
if (!FindClose(search)) failed = 1;
|
if (!FindClose(search)) failed = 1;
|
||||||
} else if (GetLastError() != ERROR_FILE_NOT_FOUND) {
|
} else if (search_error != ERROR_FILE_NOT_FOUND) {
|
||||||
failed = 1;
|
failed = 1;
|
||||||
}
|
}
|
||||||
if (!RemoveDirectoryW(directory)) failed = 1;
|
return failed ? -1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Resolve the absolute temporary path one component at a time and retain
|
||||||
|
* every ancestor handle. This makes the pathname used for enumeration
|
||||||
|
* stable even if another process tries to exchange an ancestor directory. */
|
||||||
|
static int zupt_win_open_cleanup_path(
|
||||||
|
const wchar_t *directory, wchar_t full[ZUPT_MAX_PATH + 256],
|
||||||
|
HANDLE **handles_out, size_t *handle_count_out) {
|
||||||
|
if (!_wfullpath(full, directory, ZUPT_MAX_PATH + 256)) {
|
||||||
|
errno = EINVAL;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
for (wchar_t *p = full; *p; p++) if (*p == L'/') *p = L'\\';
|
||||||
|
if ((full[0] == L'\\' && full[1] == L'\\') ||
|
||||||
|
!(full[0] && full[1] == L':' && full[2] == L'\\')) {
|
||||||
|
errno = EINVAL;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t capacity = wcslen(full) + 1u;
|
||||||
|
HANDLE *handles = (HANDLE *)calloc(capacity, sizeof(*handles));
|
||||||
|
if (!handles) return 0;
|
||||||
|
wchar_t drive_root[4] = {full[0], L':', L'\\', L'\0'};
|
||||||
|
HANDLE current = CreateFileW(
|
||||||
|
drive_root,
|
||||||
|
FILE_LIST_DIRECTORY | FILE_TRAVERSE | FILE_READ_ATTRIBUTES |
|
||||||
|
SYNCHRONIZE,
|
||||||
|
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
|
||||||
|
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL);
|
||||||
|
if (current == INVALID_HANDLE_VALUE ||
|
||||||
|
!zupt_win_plain_directory(current)) {
|
||||||
|
DWORD open_error = current == INVALID_HANDLE_VALUE
|
||||||
|
? GetLastError() : ERROR_ACCESS_DENIED;
|
||||||
|
if (current != INVALID_HANDLE_VALUE) CloseHandle(current);
|
||||||
|
free(handles);
|
||||||
|
errno = open_error == ERROR_FILE_NOT_FOUND ||
|
||||||
|
open_error == ERROR_PATH_NOT_FOUND
|
||||||
|
? ENOENT : EACCES;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
size_t count = 0;
|
||||||
|
handles[count++] = current;
|
||||||
|
|
||||||
|
wchar_t *scan = full + 3;
|
||||||
|
while (*scan) {
|
||||||
|
wchar_t *separator = wcschr(scan, L'\\');
|
||||||
|
if (separator) *separator = L'\0';
|
||||||
|
HANDLE next = zupt_win_open_cleanup_entry(
|
||||||
|
current, scan, 1, 0);
|
||||||
|
if (separator) *separator = L'\\';
|
||||||
|
if (next == INVALID_HANDLE_VALUE ||
|
||||||
|
!zupt_win_plain_directory(next)) {
|
||||||
|
if (next != INVALID_HANDLE_VALUE) CloseHandle(next);
|
||||||
|
while (count > 0) CloseHandle(handles[--count]);
|
||||||
|
free(handles);
|
||||||
|
if (next != INVALID_HANDLE_VALUE) errno = EACCES;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
handles[count++] = next;
|
||||||
|
current = next;
|
||||||
|
if (!separator) break;
|
||||||
|
scan = separator + 1;
|
||||||
|
}
|
||||||
|
*handles_out = handles;
|
||||||
|
*handle_count_out = count;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
/* Resolve every component without following symlinks and return both the
|
||||||
|
* pinned target and its pinned parent. The caller can therefore remove the
|
||||||
|
* final directory with unlinkat() instead of resolving its pathname again. */
|
||||||
|
static int zupt_open_temp_tree(const char *path, int *parent_out,
|
||||||
|
int *directory_out, char *leaf,
|
||||||
|
size_t leaf_capacity) {
|
||||||
|
if (!path || !*path || !parent_out || !directory_out || !leaf ||
|
||||||
|
leaf_capacity == 0) {
|
||||||
|
errno = EINVAL;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int current = open(path[0] == '/' ? "/" : ".",
|
||||||
|
O_RDONLY | O_DIRECTORY | O_CLOEXEC);
|
||||||
|
if (current < 0) return 0;
|
||||||
|
|
||||||
|
const char *cursor = path;
|
||||||
|
while (*cursor == '/') cursor++;
|
||||||
|
while (*cursor) {
|
||||||
|
const char *start = cursor;
|
||||||
|
while (*cursor && *cursor != '/') cursor++;
|
||||||
|
size_t component_length = (size_t)(cursor - start);
|
||||||
|
while (*cursor == '/') cursor++;
|
||||||
|
int final_component = *cursor == '\0';
|
||||||
|
if ((component_length == 1u && start[0] == '.') ||
|
||||||
|
component_length == 0u) {
|
||||||
|
if (final_component) {
|
||||||
|
close(current);
|
||||||
|
errno = EINVAL;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (component_length == 2u && start[0] == '.' && start[1] == '.') {
|
||||||
|
close(current);
|
||||||
|
errno = EINVAL;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (component_length >= leaf_capacity) {
|
||||||
|
close(current);
|
||||||
|
errno = ENAMETOOLONG;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
memcpy(leaf, start, component_length);
|
||||||
|
leaf[component_length] = '\0';
|
||||||
|
int next = openat(current, leaf,
|
||||||
|
O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
|
||||||
|
if (next < 0) {
|
||||||
|
int saved_errno = errno;
|
||||||
|
close(current);
|
||||||
|
errno = saved_errno;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (final_component) {
|
||||||
|
*parent_out = current;
|
||||||
|
*directory_out = next;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
close(current);
|
||||||
|
current = next;
|
||||||
|
}
|
||||||
|
close(current);
|
||||||
|
errno = EINVAL;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Delete leaves before attempting to open them as directories. unlinkat()
|
||||||
|
* never follows a symlink; a directory is recursively visited only through
|
||||||
|
* an O_NOFOLLOW descriptor returned by openat(). */
|
||||||
|
static int zupt_remove_temp_tree_fd(int directory_fd) {
|
||||||
|
DIR *stream = fdopendir(directory_fd);
|
||||||
|
if (!stream) {
|
||||||
|
close(directory_fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int failed = 0;
|
||||||
|
int parent_fd = dirfd(stream);
|
||||||
|
for (;;) {
|
||||||
|
errno = 0;
|
||||||
|
struct dirent *entry = readdir(stream);
|
||||||
|
if (!entry) {
|
||||||
|
if (errno != 0) failed = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (strcmp(entry->d_name, ".") == 0 ||
|
||||||
|
strcmp(entry->d_name, "..") == 0)
|
||||||
|
continue;
|
||||||
|
if (unlinkat(parent_fd, entry->d_name, 0) == 0 || errno == ENOENT)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int child_fd = openat(parent_fd, entry->d_name,
|
||||||
|
O_RDONLY | O_DIRECTORY | O_NOFOLLOW |
|
||||||
|
O_CLOEXEC);
|
||||||
|
if (child_fd < 0) {
|
||||||
|
if (errno != ENOENT) failed = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (zupt_remove_temp_tree_fd(child_fd) != 0) failed = 1;
|
||||||
|
if (unlinkat(parent_fd, entry->d_name, AT_REMOVEDIR) != 0 &&
|
||||||
|
errno != ENOENT)
|
||||||
|
failed = 1;
|
||||||
|
}
|
||||||
|
if (closedir(stream) != 0) failed = 1;
|
||||||
return failed ? -1 : 0;
|
return failed ? -1 : 0;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -169,41 +459,43 @@ static int zupt_remove_temp_tree(const char *directory) {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
wchar_t *wide = zupt_win_utf8_to_wide_alloc(directory);
|
wchar_t *wide = zupt_win_utf8_to_wide_alloc(directory);
|
||||||
if (!wide) return -1;
|
if (!wide) return -1;
|
||||||
int result = zupt_remove_tree_wide(wide);
|
wchar_t full[ZUPT_MAX_PATH + 256];
|
||||||
|
HANDLE *handles = NULL;
|
||||||
|
size_t handle_count = 0;
|
||||||
|
if (!zupt_win_open_cleanup_path(wide, full, &handles, &handle_count)) {
|
||||||
|
int result = errno == ENOENT ? 0 : -1;
|
||||||
|
free(wide);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
HANDLE root_handle = handles[handle_count - 1u];
|
||||||
|
int result = zupt_remove_tree_wide(root_handle, full);
|
||||||
|
BY_HANDLE_FILE_INFORMATION root_identity;
|
||||||
|
if (result == 0 && !GetFileInformationByHandle(root_handle,
|
||||||
|
&root_identity))
|
||||||
|
result = -1;
|
||||||
|
const wchar_t *root_name = wcsrchr(full, L'\\');
|
||||||
|
if (!root_name || root_name[1] == L'\0') result = -1;
|
||||||
|
else root_name++;
|
||||||
|
if (!CloseHandle(handles[--handle_count])) result = -1;
|
||||||
|
if (result == 0 && !zupt_win_delete_cleanup_entry(
|
||||||
|
handles[handle_count - 1u], root_name, &root_identity))
|
||||||
|
result = -1;
|
||||||
|
while (handle_count > 0)
|
||||||
|
if (!CloseHandle(handles[--handle_count])) result = -1;
|
||||||
|
free(handles);
|
||||||
free(wide);
|
free(wide);
|
||||||
return result;
|
return result;
|
||||||
#else
|
#else
|
||||||
DIR *stream = opendir(directory);
|
int parent_fd = -1;
|
||||||
if (!stream) return errno == ENOENT ? 0 : -1;
|
int directory_fd = -1;
|
||||||
int failed = 0;
|
char leaf[ZUPT_MAX_PATH];
|
||||||
struct dirent *entry;
|
if (!zupt_open_temp_tree(directory, &parent_fd, &directory_fd,
|
||||||
while ((entry = readdir(stream)) != NULL) {
|
leaf, sizeof(leaf)))
|
||||||
if (strcmp(entry->d_name, ".") == 0 ||
|
return errno == ENOENT ? 0 : -1;
|
||||||
strcmp(entry->d_name, "..") == 0)
|
int failed = zupt_remove_temp_tree_fd(directory_fd) != 0;
|
||||||
continue;
|
if (unlinkat(parent_fd, leaf, AT_REMOVEDIR) != 0 && errno != ENOENT)
|
||||||
size_t needed = strlen(directory) + strlen(entry->d_name) + 2u;
|
failed = 1;
|
||||||
char *child = (char *)malloc(needed);
|
if (close(parent_fd) != 0) failed = 1;
|
||||||
if (!child) {
|
|
||||||
failed = 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!zupt_join_temp_path(child, needed, directory, entry->d_name)) {
|
|
||||||
free(child);
|
|
||||||
failed = 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
struct stat info;
|
|
||||||
if (lstat(child, &info) != 0) {
|
|
||||||
failed = 1;
|
|
||||||
} else if (S_ISDIR(info.st_mode)) {
|
|
||||||
if (zupt_remove_temp_tree(child) != 0) failed = 1;
|
|
||||||
} else if (unlink(child) != 0) {
|
|
||||||
failed = 1;
|
|
||||||
}
|
|
||||||
free(child);
|
|
||||||
}
|
|
||||||
if (closedir(stream) != 0) failed = 1;
|
|
||||||
if (rmdir(directory) != 0) failed = 1;
|
|
||||||
return failed ? -1 : 0;
|
return failed ? -1 : 0;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
@ -429,7 +721,11 @@ static int prompt_password(const char *prompt, char *buf, size_t cap) {
|
||||||
if (!buf || cap < 2) return 0;
|
if (!buf || cap < 2) return 0;
|
||||||
buf[0] = '\0';
|
buf[0] = '\0';
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
if (!_isatty(_fileno(stdin))) {
|
HANDLE input_handle = GetStdHandle(STD_INPUT_HANDLE);
|
||||||
|
DWORD input_mode = 0;
|
||||||
|
if (input_handle == NULL || input_handle == INVALID_HANDLE_VALUE ||
|
||||||
|
GetFileType(input_handle) != FILE_TYPE_CHAR ||
|
||||||
|
!GetConsoleMode(input_handle, &input_mode)) {
|
||||||
fprintf(stderr, "Error: password prompt requires a terminal.\n");
|
fprintf(stderr, "Error: password prompt requires a terminal.\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -445,6 +741,11 @@ static int prompt_password(const char *prompt, char *buf, size_t cap) {
|
||||||
int too_long = 0;
|
int too_long = 0;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
int c = _getch();
|
int c = _getch();
|
||||||
|
if (c == EOF) {
|
||||||
|
zupt_secure_wipe(buf, cap);
|
||||||
|
fprintf(stderr, "\nError: cannot read password prompt.\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if (c == '\r' || c == '\n') break;
|
if (c == '\r' || c == '\n') break;
|
||||||
if (c == 0 || c == 0xe0) {
|
if (c == 0 || c == 0xe0) {
|
||||||
(void)_getch();
|
(void)_getch();
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,31 @@ static size_t put_varint(uint8_t *out, uint64_t value) {
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int hex_nibble(unsigned char value) {
|
||||||
|
if (value >= '0' && value <= '9') return (int)(value - '0');
|
||||||
|
if (value >= 'a' && value <= 'f') return (int)(value - 'a') + 10;
|
||||||
|
if (value >= 'A' && value <= 'F') return (int)(value - 'A') + 10;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int decode_hex_entry(const char *hex, uint8_t *out, size_t capacity,
|
||||||
|
size_t *out_size) {
|
||||||
|
size_t hex_size = strlen(hex);
|
||||||
|
if (hex_size == 0 || (hex_size & 1u) != 0 ||
|
||||||
|
hex_size / 2u >= capacity)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
size_t decoded_size = hex_size / 2u;
|
||||||
|
for (size_t i = 0; i < decoded_size; i++) {
|
||||||
|
int high = hex_nibble((unsigned char)hex[i * 2u]);
|
||||||
|
int low = hex_nibble((unsigned char)hex[i * 2u + 1u]);
|
||||||
|
if (high < 0 || low < 0) return -1;
|
||||||
|
out[i] = (uint8_t)((high << 4) | low);
|
||||||
|
}
|
||||||
|
*out_size = decoded_size;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int write_block(FILE *stream, uint8_t type, const uint8_t *payload,
|
static int write_block(FILE *stream, uint8_t type, const uint8_t *payload,
|
||||||
size_t payload_size, uint64_t unpacked_size,
|
size_t payload_size, uint64_t unpacked_size,
|
||||||
uint64_t checksum) {
|
uint64_t checksum) {
|
||||||
|
|
@ -66,11 +91,24 @@ static int write_block(FILE *stream, uint8_t type, const uint8_t *payload,
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
static const uint8_t content[] = "fixture content\n";
|
static const uint8_t content[] = "fixture content\n";
|
||||||
const char *entry = argc == 3 && strncmp(argv[2], "--entry=", 8) == 0
|
uint8_t decoded_entry[ZUPT_MAX_PATH];
|
||||||
? argv[2] + 8 : NULL;
|
const uint8_t *entry = NULL;
|
||||||
if (!entry || argv[1][0] == '\0' || entry[0] == '\0' ||
|
size_t path_size = 0;
|
||||||
strlen(entry) >= ZUPT_MAX_PATH) {
|
|
||||||
fprintf(stderr, "usage: %s ARCHIVE --entry=ENTRY_PATH\n", argv[0]);
|
if (argc == 3 && strncmp(argv[2], "--entry=", 8) == 0) {
|
||||||
|
entry = (const uint8_t *)argv[2] + 8;
|
||||||
|
path_size = strlen(argv[2] + 8);
|
||||||
|
} else if (argc == 3 &&
|
||||||
|
strncmp(argv[2], "--entry-hex=", 12) == 0 &&
|
||||||
|
decode_hex_entry(argv[2] + 12, decoded_entry,
|
||||||
|
sizeof(decoded_entry), &path_size) == 0) {
|
||||||
|
entry = decoded_entry;
|
||||||
|
}
|
||||||
|
if (!entry || argv[1][0] == '\0' || path_size == 0 ||
|
||||||
|
path_size >= ZUPT_MAX_PATH) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"usage: %s ARCHIVE --entry=ENTRY_PATH|--entry-hex=HEX_BYTES\n",
|
||||||
|
argv[0]);
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,7 +144,6 @@ int main(int argc, char **argv) {
|
||||||
|
|
||||||
uint8_t index[ZUPT_MAX_PATH + 128];
|
uint8_t index[ZUPT_MAX_PATH + 128];
|
||||||
size_t index_size = 0;
|
size_t index_size = 0;
|
||||||
size_t path_size = strlen(entry);
|
|
||||||
index_size += put_varint(index + index_size, 1);
|
index_size += put_varint(index + index_size, 1);
|
||||||
index_size += put_varint(index + index_size, path_size);
|
index_size += put_varint(index + index_size, path_size);
|
||||||
memcpy(index + index_size, entry, path_size);
|
memcpy(index + index_size, entry, path_size);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
||||||
#
|
#
|
||||||
# Flake-stress harness — §3 of PROMPT.md.
|
# Repeated-suite flake-stress harness.
|
||||||
#
|
#
|
||||||
# Runs every short test suite N times (default 50) and aborts on the
|
# Runs every short test suite N times (default 50) and aborts on the
|
||||||
# first non-deterministic outcome. Specifically targeted at the audit
|
# first non-deterministic outcome. Specifically targeted at the audit
|
||||||
|
|
@ -21,7 +21,7 @@ set -u
|
||||||
# F-02's repro needed 50 runs to be statistically convincing (~10%
|
# F-02's repro needed 50 runs to be statistically convincing (~10%
|
||||||
# baseline flake rate), but at 20 runs we still have ~88% chance of
|
# baseline flake rate), but at 20 runs we still have ~88% chance of
|
||||||
# catching a 10%-flake — fine for routine CI. For a hardened audit
|
# catching a 10%-flake — fine for routine CI. For a hardened audit
|
||||||
# pass, invoke with 50 or 100 (see PROMPT.md §3).
|
# pass, invoke with 50 or 100 for a deeper audit run.
|
||||||
N="${1:-20}"
|
N="${1:-20}"
|
||||||
ZUPT_BIN="${ZUPT_BIN:-./zupt}"
|
ZUPT_BIN="${ZUPT_BIN:-./zupt}"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,11 @@
|
||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
|
|
||||||
bin=${1:-./zupt}
|
bin=${1:-./zupt}
|
||||||
|
repo_root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd -P)
|
||||||
|
case $bin in
|
||||||
|
/*) ;;
|
||||||
|
*) bin="$(pwd -P)/${bin#./}" ;;
|
||||||
|
esac
|
||||||
tmp=$(mktemp -d "${TMPDIR:-/tmp}/zupt-bench-safety.XXXXXXXX")
|
tmp=$(mktemp -d "${TMPDIR:-/tmp}/zupt-bench-safety.XXXXXXXX")
|
||||||
trap 'rm -rf -- "$tmp"' EXIT HUP INT TERM
|
trap 'rm -rf -- "$tmp"' EXIT HUP INT TERM
|
||||||
|
|
||||||
|
|
@ -11,9 +16,36 @@ fail() {
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# CodeQL #7 reported the old lstat(child) -> recursive pathname operation as
|
||||||
|
# cpp/toctou-race-condition. Keep the platform-specific cleanup primitives in
|
||||||
|
# the source gate as well as exercising the runtime symlink boundary below.
|
||||||
|
cleanup_source=$repo_root/src/zupt_main.c
|
||||||
|
grep -Fq 'static int zupt_remove_temp_tree_fd(int directory_fd)' \
|
||||||
|
"$cleanup_source" || fail 'POSIX descriptor-relative cleanup is missing'
|
||||||
|
grep -Fq 'unlinkat(parent_fd, entry->d_name, 0)' "$cleanup_source" ||
|
||||||
|
fail 'POSIX leaf cleanup is not unlinkat-relative'
|
||||||
|
grep -Fq 'directory_handle, data.cFileName, 1, 0)' "$cleanup_source" ||
|
||||||
|
fail 'Windows recursive cleanup is not handle-relative'
|
||||||
|
grep -Fq 'FILE_OPEN_REPARSE_POINT' "$cleanup_source" ||
|
||||||
|
fail 'Windows cleanup no longer opens reparse points without following'
|
||||||
|
grep -Fq 'zupt_win_delete_cleanup_entry(' "$cleanup_source" ||
|
||||||
|
fail 'Windows cleanup lacks identity-checked handle deletion'
|
||||||
|
grep -Fq 'current.nFileIndexLow == expected->nFileIndexLow' "$cleanup_source" ||
|
||||||
|
fail 'Windows cleanup no longer rejects a close/reopen name exchange'
|
||||||
|
if grep -Fq 'RemoveDirectoryW(full)' "$cleanup_source"; then
|
||||||
|
fail 'Windows root cleanup restored post-handle pathname deletion'
|
||||||
|
fi
|
||||||
|
if grep -Fq 'lstat(child' "$cleanup_source" ||
|
||||||
|
grep -Fq 'zupt_remove_temp_tree(child' "$cleanup_source"; then
|
||||||
|
fail 'temporary cleanup restored a check-then-use pathname traversal'
|
||||||
|
fi
|
||||||
|
|
||||||
case $(uname -s 2>/dev/null || printf unknown) in
|
case $(uname -s 2>/dev/null || printf unknown) in
|
||||||
MINGW*|MSYS*|CYGWIN*)
|
MINGW*|MSYS*|CYGWIN*)
|
||||||
printf 'SKIP: historical POSIX /tmp symlink benchmark test is not native on Windows\n'
|
"$bin" bench --compare >/dev/null 2>&1 ||
|
||||||
|
fail 'native Windows handle-relative benchmark cleanup failed'
|
||||||
|
printf 'SKIP: adversarial POSIX symlink injection is not native on Windows\n'
|
||||||
|
printf 'private Windows handle-relative benchmark workspace: PASS\n'
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
@ -46,4 +78,50 @@ if [[ -d $old_directory ]]; then
|
||||||
mv "$old_directory" "$tmp/historical-remnant"
|
mv "$old_directory" "$tmp/historical-remnant"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
printf 'private benchmark workspace: PASS\n'
|
# Inject a directory symlink into the private workspace while a real benchmark
|
||||||
|
# is active. Cleanup must remove the link itself and never visit its target.
|
||||||
|
mkdir "$tmp/symlink-target"
|
||||||
|
printf 'cleanup sentinel must survive\n' > "$tmp/symlink-target/sentinel"
|
||||||
|
cp "$tmp/symlink-target/sentinel" "$tmp/symlink-target.expected"
|
||||||
|
dd if=/dev/urandom of="$tmp/injection-input" bs=65536 count=128 2>/dev/null
|
||||||
|
|
||||||
|
physical_tmp=$(CDPATH='' cd -P -- /tmp && pwd -P)
|
||||||
|
: > "$tmp/preexisting-workspaces"
|
||||||
|
for candidate in "$physical_tmp"/zupt-bench-*; do
|
||||||
|
if [[ -d $candidate && ! -L $candidate ]]; then
|
||||||
|
printf '%s\n' "$candidate" >> "$tmp/preexisting-workspaces"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
(cd "$tmp" && "$bin" bench injection-input >/dev/null 2>&1) &
|
||||||
|
bench_pid=$!
|
||||||
|
injected=0
|
||||||
|
injected_workspace=
|
||||||
|
attempt=0
|
||||||
|
while (( attempt < 1000 )); do
|
||||||
|
for candidate in "$physical_tmp"/zupt-bench-*; do
|
||||||
|
[[ -d $candidate && ! -L $candidate ]] || continue
|
||||||
|
if grep -Fqx -- "$candidate" "$tmp/preexisting-workspaces"; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if ln -s "$tmp/symlink-target" "$candidate/attacker-link" \
|
||||||
|
2>/dev/null; then
|
||||||
|
injected=1
|
||||||
|
injected_workspace=$candidate
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
(( injected == 1 )) && break
|
||||||
|
kill -0 "$bench_pid" 2>/dev/null || break
|
||||||
|
sleep 0.01
|
||||||
|
attempt=$((attempt + 1))
|
||||||
|
done
|
||||||
|
wait "$bench_pid" || fail 'benchmark with injected symlink failed'
|
||||||
|
(( injected == 1 )) || fail 'could not observe the private benchmark workspace'
|
||||||
|
if [[ -e $injected_workspace || -L $injected_workspace ]]; then
|
||||||
|
fail 'injected workspace was not the benchmark tree that was removed'
|
||||||
|
fi
|
||||||
|
cmp "$tmp/symlink-target.expected" "$tmp/symlink-target/sentinel" ||
|
||||||
|
fail 'temporary cleanup followed an injected directory symlink'
|
||||||
|
|
||||||
|
printf 'private descriptor/handle-relative benchmark workspace: PASS\n'
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
||||||
#
|
#
|
||||||
# F-08 regression test (VaptVupt 2.3.0).
|
# F-08 regression test (ZUPT 2.3.0).
|
||||||
#
|
#
|
||||||
# A v1.5+ archive is tampered at each previously-cosmetic header/footer byte;
|
# A v1.5+ archive is tampered at each previously-cosmetic header/footer byte;
|
||||||
# every mutation MUST be detected (top-MAC verifies header+footer[0..23]).
|
# every mutation MUST be detected (top-MAC verifies header+footer[0..23]).
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
||||||
#
|
#
|
||||||
# F-09 regression test (VaptVupt 2.3.1).
|
# F-09 regression test (ZUPT 2.3.1).
|
||||||
#
|
#
|
||||||
# F-09 closed the per-block frame preface tamper window by:
|
# F-09 closed the per-block frame preface tamper window by:
|
||||||
# 1. Binding the canonical preface (block_type, codec_id, block_flags,
|
# 1. Binding the canonical preface (block_type, codec_id, block_flags,
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
||||||
#
|
#
|
||||||
# F-11 regression test (VaptVupt 2.4.2).
|
# F-11 regression test (ZUPT 2.4.2).
|
||||||
#
|
#
|
||||||
# F-11: pre-2.4.2 the AIT-fail message said "archive header or footer has
|
# F-11: pre-2.4.2 the AIT-fail message said "archive header or footer has
|
||||||
# been tampered with" in both the actual-tamper case AND the wrong-password
|
# been tampered with" in both the actual-tamper case AND the wrong-password
|
||||||
|
|
|
||||||
|
|
@ -41,17 +41,76 @@ file_mode() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
windows_private_acl() {
|
||||||
|
local output=$1 windows_path
|
||||||
|
command -v cygpath >/dev/null 2>&1 || return 1
|
||||||
|
command -v powershell.exe >/dev/null 2>&1 || return 1
|
||||||
|
windows_path=$(cygpath -aw -- "$output") || return 1
|
||||||
|
# PowerShell variables must remain literal until powershell.exe evaluates
|
||||||
|
# this single-quoted Bash argument.
|
||||||
|
# shellcheck disable=SC2016
|
||||||
|
ZUPT_KEY_ACL_PATH=$windows_path powershell.exe -NoLogo -NoProfile \
|
||||||
|
-NonInteractive -Command '
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$acl = Get-Acl -LiteralPath $env:ZUPT_KEY_ACL_PATH
|
||||||
|
$sidType = [System.Security.Principal.SecurityIdentifier]
|
||||||
|
$rules = @($acl.GetAccessRules($true, $true, $sidType))
|
||||||
|
$currentSid =
|
||||||
|
[System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value
|
||||||
|
if (-not $acl.AreAccessRulesProtected) {
|
||||||
|
throw "private-key DACL permits inheritance"
|
||||||
|
}
|
||||||
|
if ($rules.Count -ne 1) {
|
||||||
|
throw "private-key DACL does not contain exactly one ACE"
|
||||||
|
}
|
||||||
|
$rule = $rules[0]
|
||||||
|
if ($rule.IsInherited) {
|
||||||
|
throw "private-key ACE is inherited"
|
||||||
|
}
|
||||||
|
if ($rule.AccessControlType -ne
|
||||||
|
[System.Security.AccessControl.AccessControlType]::Allow) {
|
||||||
|
throw "private-key ACE is not an allow rule"
|
||||||
|
}
|
||||||
|
if ($rule.IdentityReference.Value -ne $currentSid) {
|
||||||
|
throw "private-key ACE is not restricted to the current user"
|
||||||
|
}
|
||||||
|
if ($rule.InheritanceFlags -ne
|
||||||
|
[System.Security.AccessControl.InheritanceFlags]::None -or
|
||||||
|
$rule.PropagationFlags -ne
|
||||||
|
[System.Security.AccessControl.PropagationFlags]::None) {
|
||||||
|
throw "private-key ACE unexpectedly propagates"
|
||||||
|
}
|
||||||
|
$fullControl = [int64](
|
||||||
|
[System.Security.AccessControl.FileSystemRights]::FullControl)
|
||||||
|
$actualRights = [int64]($rule.FileSystemRights)
|
||||||
|
if (($actualRights -band $fullControl) -ne $fullControl) {
|
||||||
|
throw "private-key ACE does not grant current-user full control"
|
||||||
|
}
|
||||||
|
' </dev/null >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
generate_with_mode() {
|
generate_with_mode() {
|
||||||
local label=$1 mask=$2 output=$3
|
local label=$1 mask=$2 output=$3
|
||||||
shift 3
|
shift 3
|
||||||
if (umask "$mask"; "$zupt_bin" keygen "$@" -o "$output" >/dev/null 2>&1); then
|
if (umask "$mask"; "$zupt_bin" keygen "$@" -o "$output" >/dev/null 2>&1); then
|
||||||
local mode
|
case $(uname -s 2>/dev/null || printf unknown) in
|
||||||
mode=$(file_mode "$output")
|
MINGW*|MSYS*|CYGWIN*)
|
||||||
if [[ $mode == 600 ]]; then
|
if windows_private_acl "$output"; then
|
||||||
pass "$label is mode 0600 under umask $mask"
|
pass "$label has a protected current-user-only DACL under umask $mask"
|
||||||
else
|
else
|
||||||
fail "$label mode under umask $mask is $mode, expected 600"
|
fail "$label lacks a protected current-user-only DACL under umask $mask"
|
||||||
fi
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
local mode
|
||||||
|
mode=$(file_mode "$output")
|
||||||
|
if [[ $mode == 600 ]]; then
|
||||||
|
pass "$label is mode 0600 under umask $mask"
|
||||||
|
else
|
||||||
|
fail "$label mode under umask $mask is $mode, expected 600"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
else
|
else
|
||||||
fail "$label generation failed under umask $mask"
|
fail "$label generation failed under umask $mask"
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,17 @@ pass() { pass_count=$((pass_count + 1)); printf 'PASS: %s\n' "$*"; }
|
||||||
fail() { fail_count=$((fail_count + 1)); printf 'FAIL: %s\n' "$*" >&2; }
|
fail() { fail_count=$((fail_count + 1)); printf 'FAIL: %s\n' "$*" >&2; }
|
||||||
skip() { skip_count=$((skip_count + 1)); printf 'SKIP: %s\n' "$*"; }
|
skip() { skip_count=$((skip_count + 1)); printf 'SKIP: %s\n' "$*"; }
|
||||||
|
|
||||||
|
has_exact_line_crlf_safe() {
|
||||||
|
local expected=$1 path=$2 line
|
||||||
|
while IFS= read -r line || [[ -n $line ]]; do
|
||||||
|
line=${line%$'\r'}
|
||||||
|
if [[ $line == "$expected" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done < "$path"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
|
version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
|
||||||
[[ -n $version ]] || { printf 'FAIL: cannot determine upstream version\n' >&2; exit 1; }
|
[[ -n $version ]] || { printf 'FAIL: cannot determine upstream version\n' >&2; exit 1; }
|
||||||
|
|
||||||
|
|
@ -62,7 +73,8 @@ check_recipe_version GUI-Deb-Control \
|
||||||
"$(awk '/^Version:/{print $2; exit}' gui/packaging/deb/control)"
|
"$(awk '/^Version:/{print $2; exit}' gui/packaging/deb/control)"
|
||||||
|
|
||||||
if grep -Fqx "VERSION=\${VERSION:-$version}" install.sh && \
|
if grep -Fqx "VERSION=\${VERSION:-$version}" install.sh && \
|
||||||
grep -Fqx "if not defined VERSION set \"VERSION=$version\"" \
|
has_exact_line_crlf_safe \
|
||||||
|
"if not defined VERSION set \"VERSION=$version\"" \
|
||||||
gui/packaging/windows/build-windows.bat && \
|
gui/packaging/windows/build-windows.bat && \
|
||||||
grep -Fqx "Depends: python3 (>= 3.9), python3-pyqt6 | python3-pyside6.qtwidgets, zupt (= $version)" \
|
grep -Fqx "Depends: python3 (>= 3.9), python3-pyqt6 | python3-pyside6.qtwidgets, zupt (= $version)" \
|
||||||
gui/packaging/deb/control; then
|
gui/packaging/deb/control; then
|
||||||
|
|
@ -183,6 +195,12 @@ tumbleweed_job=$(sed -n '/^ tumbleweed-rpm:/,/^ gui-rpm-package:/p' \
|
||||||
.github/workflows/ci.yml)
|
.github/workflows/ci.yml)
|
||||||
fedora_gui_job=$(sed -n '/^ gui-rpm-package:/,/^ linux-portable:/p' \
|
fedora_gui_job=$(sed -n '/^ gui-rpm-package:/,/^ linux-portable:/p' \
|
||||||
.github/workflows/ci.yml)
|
.github/workflows/ci.yml)
|
||||||
|
if grep -Fq 'os.chdir(service_dir)' <<<"$tumbleweed_job"; then
|
||||||
|
pass 'OBS service executor enters its isolated working directory'
|
||||||
|
else
|
||||||
|
fail 'OBS service executor does not enter its isolated working directory'
|
||||||
|
fi
|
||||||
|
|
||||||
# These matches intentionally assert the literal Actions variable in YAML.
|
# These matches intentionally assert the literal Actions variable in YAML.
|
||||||
# shellcheck disable=SC2016
|
# shellcheck disable=SC2016
|
||||||
if grep -Fq 'git config --global --add safe.directory "$GITHUB_WORKSPACE"' \
|
if grep -Fq 'git config --global --add safe.directory "$GITHUB_WORKSPACE"' \
|
||||||
|
|
|
||||||
|
|
@ -56,9 +56,30 @@ if "$binary" test --pass-fd not-a-number archive.zupt >/dev/null 2>&1; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if "$binary" test --password-prompt archive.zupt </dev/null >/dev/null 2>&1; then
|
prompt_log=$test_root/non-interactive-prompt.log
|
||||||
|
if command -v timeout >/dev/null 2>&1; then
|
||||||
|
set +e
|
||||||
|
timeout 10 "$binary" test --password-prompt archive.zupt \
|
||||||
|
</dev/null >"$prompt_log" 2>&1
|
||||||
|
prompt_status=$?
|
||||||
|
set -e
|
||||||
|
else
|
||||||
|
set +e
|
||||||
|
"$binary" test --password-prompt archive.zupt \
|
||||||
|
</dev/null >"$prompt_log" 2>&1
|
||||||
|
prompt_status=$?
|
||||||
|
set -e
|
||||||
|
fi
|
||||||
|
if ((prompt_status == 124)); then
|
||||||
|
printf '%s\n' 'FAIL: non-interactive password prompt timed out' >&2
|
||||||
|
exit 1
|
||||||
|
elif ((prompt_status == 0)); then
|
||||||
printf '%s\n' 'FAIL: non-interactive password prompt unexpectedly succeeded' >&2
|
printf '%s\n' 'FAIL: non-interactive password prompt unexpectedly succeeded' >&2
|
||||||
exit 1
|
exit 1
|
||||||
|
elif ! grep -Fq 'password prompt requires a terminal.' "$prompt_log"; then
|
||||||
|
printf 'FAIL: non-interactive password prompt returned status %d without a terminal rejection\n' \
|
||||||
|
"$prompt_status" >&2
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
case $(uname -s) in
|
case $(uname -s) in
|
||||||
|
|
|
||||||
|
|
@ -104,33 +104,62 @@ else
|
||||||
fail 'control-byte archive path is rejected without terminal injection'
|
fail 'control-byte archive path is rejected without terminal injection'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
expect_display_unsafe_path_rejected() {
|
file_contains_hex_bytes() {
|
||||||
local label=$1 name=$2 entry=$3
|
python3 - "$1" "$2" <<'PY'
|
||||||
|
import pathlib
|
||||||
|
import sys
|
||||||
|
|
||||||
|
data = pathlib.Path(sys.argv[1]).read_bytes()
|
||||||
|
needle = bytes.fromhex(sys.argv[2])
|
||||||
|
raise SystemExit(0 if needle in data else 1)
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
expect_display_unsafe_hex_path_rejected() {
|
||||||
|
local label=$1 name=$2 entry_hex=$3 forbidden_hex=$4
|
||||||
local archive=$TEST_ROOT/$name.zupt log=$TEST_ROOT/$name.log status
|
local archive=$TEST_ROOT/$name.zupt log=$TEST_ROOT/$name.log status
|
||||||
MSYS2_ARG_CONV_EXCL='--entry=' "$FIXTURE" "$archive" "--entry=$entry"
|
MSYS2_ARG_CONV_EXCL='--entry-hex=' \
|
||||||
|
"$FIXTURE" "$archive" "--entry-hex=$entry_hex"
|
||||||
|
if ! file_contains_hex_bytes "$archive" "$entry_hex"; then
|
||||||
|
printf ' fixture did not preserve the requested path bytes: %s\n' \
|
||||||
|
"$entry_hex" >&2
|
||||||
|
fail "$label"
|
||||||
|
return
|
||||||
|
fi
|
||||||
set +e
|
set +e
|
||||||
"$ZUPT_BIN" list "$archive" > "$log" 2>&1
|
"$ZUPT_BIN" list "$archive" > "$log" 2>&1
|
||||||
status=$?
|
status=$?
|
||||||
set -e
|
set -e
|
||||||
if ((status != 0)) && ! LC_ALL=C grep -Fq -- "$entry" "$log"; then
|
if ((status != 0)) && ! file_contains_hex_bytes "$log" "$forbidden_hex"; then
|
||||||
pass "$label"
|
pass "$label"
|
||||||
else
|
else
|
||||||
fail "$label"
|
fail "$label"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
expect_display_unsafe_path_rejected \
|
if MSYS2_ARG_CONV_EXCL='--entry-hex=' \
|
||||||
|
"$FIXTURE" "$TEST_ROOT/invalid-hex.zupt" '--entry-hex=0' \
|
||||||
|
>/dev/null 2>&1 ||
|
||||||
|
MSYS2_ARG_CONV_EXCL='--entry-hex=' \
|
||||||
|
"$FIXTURE" "$TEST_ROOT/invalid-hex.zupt" '--entry-hex=GG' \
|
||||||
|
>/dev/null 2>&1; then
|
||||||
|
fail 'archive path fixture rejects malformed hex input'
|
||||||
|
else
|
||||||
|
pass 'archive path fixture rejects malformed hex input'
|
||||||
|
fi
|
||||||
|
|
||||||
|
expect_display_unsafe_hex_path_rejected \
|
||||||
'raw C1 archive path is rejected without terminal injection' \
|
'raw C1 archive path is rejected without terminal injection' \
|
||||||
raw-c1 $'safe\23331m.txt'
|
raw-c1 736166659b33316d2e747874 9b
|
||||||
expect_display_unsafe_path_rejected \
|
expect_display_unsafe_hex_path_rejected \
|
||||||
'UTF-8 C1 archive path is rejected without terminal injection' \
|
'UTF-8 C1 archive path is rejected without terminal injection' \
|
||||||
utf8-c1 $'safe\302\23331m.txt'
|
utf8-c1 73616665c29b33316d2e747874 c29b
|
||||||
expect_display_unsafe_path_rejected \
|
expect_display_unsafe_hex_path_rejected \
|
||||||
'Unicode bidi-control archive path is rejected without display spoofing' \
|
'Unicode bidi-control archive path is rejected without display spoofing' \
|
||||||
bidi $'safe\342\200\256exe.txt'
|
bidi 73616665e280ae6578652e747874 e280ae
|
||||||
expect_display_unsafe_path_rejected \
|
expect_display_unsafe_hex_path_rejected \
|
||||||
'invalid UTF-8 archive path is rejected without raw display' \
|
'invalid UTF-8 archive path is rejected without raw display' \
|
||||||
invalid-utf8 $'safe\300\257.txt'
|
invalid-utf8 73616665c0af2e747874 c0af
|
||||||
|
|
||||||
make_fixture "$TEST_ROOT/leaf.zupt" 'innocent.txt'
|
make_fixture "$TEST_ROOT/leaf.zupt" 'innocent.txt'
|
||||||
printf '%s\n' DO_NOT_OVERWRITE > "$TEST_ROOT/sentinel"
|
printf '%s\n' DO_NOT_OVERWRITE > "$TEST_ROOT/sentinel"
|
||||||
|
|
@ -219,14 +248,33 @@ else
|
||||||
fail 'backslash separators are normalized within the extraction root'
|
fail 'backslash separators are normalized within the extraction root'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
make_fixture "$TEST_ROOT/legitimate.zupt" 'safe dir/ação.txt'
|
legitimate_entry_hex=73616665206469722f61c3a7c3a36f2df09f98802e747874
|
||||||
|
MSYS2_ARG_CONV_EXCL='--entry-hex=' \
|
||||||
|
"$FIXTURE" "$TEST_ROOT/legitimate.zupt" \
|
||||||
|
"--entry-hex=$legitimate_entry_hex"
|
||||||
mkdir "$TEST_ROOT/legitimate-out"
|
mkdir "$TEST_ROOT/legitimate-out"
|
||||||
if "$ZUPT_BIN" extract -o "$TEST_ROOT/legitimate-out" \
|
if file_contains_hex_bytes "$TEST_ROOT/legitimate.zupt" \
|
||||||
|
"$legitimate_entry_hex" &&
|
||||||
|
"$ZUPT_BIN" list "$TEST_ROOT/legitimate.zupt" \
|
||||||
|
> "$TEST_ROOT/legitimate-list.log" 2>&1 &&
|
||||||
|
file_contains_hex_bytes "$TEST_ROOT/legitimate-list.log" \
|
||||||
|
"$legitimate_entry_hex" &&
|
||||||
|
"$ZUPT_BIN" extract -o "$TEST_ROOT/legitimate-out" \
|
||||||
"$TEST_ROOT/legitimate.zupt" > "$TEST_ROOT/legitimate.log" 2>&1 &&
|
"$TEST_ROOT/legitimate.zupt" > "$TEST_ROOT/legitimate.log" 2>&1 &&
|
||||||
[[ $(<"$TEST_ROOT/legitimate-out/safe dir/ação.txt") == 'fixture content' ]]; then
|
python3 - "$TEST_ROOT/legitimate-out" "$legitimate_entry_hex" <<'PY'
|
||||||
pass 'safe nested UTF-8 path extracts normally'
|
import pathlib
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# All process arguments are ASCII. Decode the exact UTF-8 archive bytes here
|
||||||
|
# so the native MinGW fixture's narrow-argv transcoding cannot affect the test.
|
||||||
|
relative_path = bytes.fromhex(sys.argv[2]).decode("utf-8")
|
||||||
|
extracted = pathlib.Path(sys.argv[1]).joinpath(*relative_path.split("/"))
|
||||||
|
raise SystemExit(0 if extracted.read_bytes() == b"fixture content\n" else 1)
|
||||||
|
PY
|
||||||
|
then
|
||||||
|
pass 'safe nested BMP and non-BMP UTF-8 path lists and extracts normally'
|
||||||
else
|
else
|
||||||
fail 'safe nested UTF-8 path extracts normally'
|
fail 'safe nested BMP and non-BMP UTF-8 path lists and extracts normally'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p "$TEST_ROOT/relative-root/work"
|
mkdir -p "$TEST_ROOT/relative-root/work"
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@
|
||||||
#define HAVE_SHANI_BUILD 1
|
#define HAVE_SHANI_BUILD 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_SHANI_BUILD
|
||||||
static const uint32_t IV[8] = {
|
static const uint32_t IV[8] = {
|
||||||
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
|
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
|
||||||
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
|
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
|
||||||
|
|
@ -45,6 +46,7 @@ static void bad(const char *m) { printf(" \xE2\x9C\x97 %s\n", m); fail++; }
|
||||||
static void hex(const uint8_t *b, int n, char *out) {
|
static void hex(const uint8_t *b, int n, char *out) {
|
||||||
for (int i = 0; i < n; i++) sprintf(out + i*2, "%02x", b[i]);
|
for (int i = 0; i < n; i++) sprintf(out + i*2, "%02x", b[i]);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
zupt_detect_cpu(&zupt_cpu);
|
zupt_detect_cpu(&zupt_cpu);
|
||||||
|
|
|
||||||
|
|
@ -154,16 +154,19 @@ case "$(uname -s)" in
|
||||||
|
|
||||||
tree=$(fresh_tree raw-c1-path)
|
tree=$(fresh_tree raw-c1-path)
|
||||||
control_name=$'raw-\200.txt'
|
control_name=$'raw-\200.txt'
|
||||||
printf '\177ELF\002\001\001\000compiled' >"$tree/$control_name"
|
if { printf '\177ELF\002\001\001\000compiled' >"$tree/$control_name"; } 2>/dev/null; then
|
||||||
if "$SCANNER" --tree "$tree" >"$TEST_TMP/output" 2>&1; then
|
if "$SCANNER" --tree "$tree" >"$TEST_TMP/output" 2>&1; then
|
||||||
printf 'not ok - raw C1 path was not rejected\n'
|
printf 'not ok - raw C1 path was not rejected\n'
|
||||||
exit 1
|
exit 1
|
||||||
elif ! grep -Fq 'raw-\x80.txt' "$TEST_TMP/output" ||
|
elif ! grep -Fq 'raw-\x80.txt' "$TEST_TMP/output" ||
|
||||||
LC_ALL=C grep -q $'\200' "$TEST_TMP/output"; then
|
LC_ALL=C grep -q $'\200' "$TEST_TMP/output"; then
|
||||||
printf 'not ok - raw C1 path was not rendered safely\n'
|
printf 'not ok - raw C1 path was not rendered safely\n'
|
||||||
exit 1
|
exit 1
|
||||||
|
else
|
||||||
|
pass 'scanner escapes invalid raw C1 bytes in reported paths'
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
pass 'scanner escapes invalid raw C1 bytes in reported paths'
|
skip 'raw C1 filenames are forbidden by this filesystem'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
tree=$(fresh_tree utf8-c1-path)
|
tree=$(fresh_tree utf8-c1-path)
|
||||||
|
|
|
||||||
|
|
@ -187,6 +187,44 @@ else
|
||||||
F "ECHO bit-clear missing the explicit (tcflag_t) cast"
|
F "ECHO bit-clear missing the explicit (tcflag_t) cast"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# A restore to a device is irreversible. Classify the already-open descriptor
|
||||||
|
# rather than checking target_path and resolving that mutable name again.
|
||||||
|
if grep -Fq 'lstat(target_path' src/zupt_disk.c; then
|
||||||
|
F "disk restore has a path-check/open TOCTOU pattern"
|
||||||
|
elif grep -Fq 'tgt_fd = open(target_path' src/zupt_disk.c &&
|
||||||
|
grep -Fq 'fstat(tgt_fd, &opened_st)' src/zupt_disk.c; then
|
||||||
|
P "disk restore classifies the opened target descriptor"
|
||||||
|
else
|
||||||
|
F "disk restore descriptor-first target guard is missing"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# CodeQL #5 reported chmod(dst, mode) after reopening/resolving the SDK save
|
||||||
|
# path. Key copies must use the core's handle/descriptor-relative atomic
|
||||||
|
# publisher and apply POSIX permissions to its already-open temporary stream.
|
||||||
|
if grep -Fq 'chmod(dst, mode)' sdk/src/zuptsdk.c; then
|
||||||
|
F "SDK key save has a path-based chmod TOCTOU pattern"
|
||||||
|
elif grep -Fq 'zupt_atomic_output_open(dst, &fo)' sdk/src/zuptsdk.c &&
|
||||||
|
grep -Fq 'fchmod(fileno(fo), mode)' sdk/src/zuptsdk.c &&
|
||||||
|
grep -Fq 'zupt_atomic_output_finish(output, rc == ZUPTSDK_OK)' \
|
||||||
|
sdk/src/zuptsdk.c; then
|
||||||
|
P "SDK key save uses descriptor-relative atomic publication"
|
||||||
|
else
|
||||||
|
F "SDK key save atomic publication guard is missing"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The SDK regression must not recreate the same check/use pattern while
|
||||||
|
# inspecting its sentinels and key modes. Open once, then classify/read via
|
||||||
|
# that descriptor; this also keeps CodeQL evidence free of test-only races.
|
||||||
|
if grep -Eq '(^|[^[:alnum:]_])(stat|lstat)[[:space:]]*\(' \
|
||||||
|
sdk/tests/test_sdk_roundtrip.c; then
|
||||||
|
F "SDK regression uses path-level stat/lstat before later path operations"
|
||||||
|
elif grep -Fq 'fstat(fd, &info)' sdk/tests/test_sdk_roundtrip.c &&
|
||||||
|
grep -Fq 'fstat(fd, info)' sdk/tests/test_sdk_roundtrip.c; then
|
||||||
|
P "SDK regression inspects already-open file descriptors"
|
||||||
|
else
|
||||||
|
F "SDK regression descriptor-based inspection guard is missing"
|
||||||
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo " ───────────────────────────────────────"
|
echo " ───────────────────────────────────────"
|
||||||
echo " Static analysis: $PASS passed, $FAIL failed"
|
echo " Static analysis: $PASS passed, $FAIL failed"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue