test: close follow-up security gate findings
This commit is contained in:
parent
69fc26bfec
commit
65bc5bd427
9 changed files with 183 additions and 27 deletions
1
.github/workflows/ci.yml
vendored
1
.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 \
|
||||||
|
|
|
||||||
9
AUDIT.md
9
AUDIT.md
|
|
@ -225,6 +225,14 @@ regression injects a directory symlink and verifies that its external sentinel
|
||||||
survives. These are reviewed fixes and regression coverage,
|
survives. These are reviewed fixes and regression coverage,
|
||||||
not independent certification or proof that a 5.2.8 hosted gate passed.
|
not independent certification or proof that a 5.2.8 hosted gate passed.
|
||||||
|
|
||||||
|
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. A fresh
|
||||||
|
default-branch scan remains the authoritative closure evidence.
|
||||||
|
|
||||||
The exact 5.2.8 candidate must repeat the required suite. Native Windows and
|
The exact 5.2.8 candidate must repeat the required suite. Native Windows and
|
||||||
macOS gates, hosted GitHub CI and release promotion, authenticated OBS
|
macOS gates, hosted GitHub CI and release promotion, authenticated OBS
|
||||||
validation, and resolution of the openSUSE automatic `debugsource` rpmlint
|
validation, and resolution of the openSUSE automatic `debugsource` rpmlint
|
||||||
|
|
@ -270,6 +278,7 @@ should be rerun, but the historical resolution does not itself constitute a
|
||||||
| 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 #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 #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 | 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
|
||||||
|
|
|
||||||
13
CHANGELOG.md
13
CHANGELOG.md
|
|
@ -26,12 +26,23 @@ unchanged.
|
||||||
`unlinkat`, and using pinned, reparse-point-aware handles for Windows
|
`unlinkat`, and using pinned, reparse-point-aware handles for Windows
|
||||||
traversal. The regression injects a directory symlink into a live workspace
|
traversal. The regression injects a directory symlink into a live workspace
|
||||||
and verifies that cleanup does not visit its target.
|
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.
|
||||||
- Treat inability to create the raw-C1 scanner filename as an explicit fixture
|
- 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
|
skip on filesystems that reject the byte; when creation succeeds, the unsafe
|
||||||
diagnostic-escaping assertions still run unchanged.
|
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
|
- Reject redirected or otherwise non-console Windows password prompts before
|
||||||
entering `_getch`, handle console EOF as an error, and cover the native
|
entering `_getch`, handle console EOF as an error, and cover the native
|
||||||
redirected-input path so it cannot hang a release gate.
|
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.
|
||||||
- Run `sdk-test` from both `release-check` and the hosted GCC/Clang Linux job so
|
- 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.
|
the atomic key-save regression cannot silently fall outside release gates.
|
||||||
- Carry the v5.2.7 archive format, cryptography, bundled codec release, and SDK
|
- Carry the v5.2.7 archive format, cryptography, bundled codec release, and SDK
|
||||||
|
|
|
||||||
13
README.md
13
README.md
|
|
@ -10,9 +10,15 @@ publish atomically through an already-open private object, POSIX disk restore
|
||||||
classifies and retains the descriptor it actually opened, and benchmark cleanup
|
classifies and retains the descriptor it actually opened, and benchmark cleanup
|
||||||
traverses only pinned descriptors or handles without following links or Windows
|
traverses only pinned descriptors or handles without following links or Windows
|
||||||
reparse points. It also makes the raw-C1 scanner fixture explicitly skip a
|
reparse points. It also makes the raw-C1 scanner fixture explicitly skip a
|
||||||
filesystem that rejects creation with `EILSEQ`, and brings `sdk-test` into the
|
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
|
release and hosted Linux gates. Windows password prompts now reject redirected
|
||||||
input before entering `_getch` and treat console EOF as an error. These
|
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 of commit `69fc26b`
|
||||||
|
closed #5, #6, and #7 and exposed test-only #8, #9, and #10 in the new SDK
|
||||||
|
regression. Their follow-up uses no-follow descriptors plus `fstat`/descriptor
|
||||||
|
reads and requires a fresh default-branch scan for closure. These
|
||||||
corrections do not change archive format v1.6, cryptography, the bundled codec
|
corrections do not change archive format v1.6, cryptography, the bundled codec
|
||||||
release, or the SDK ABI.
|
release, or the SDK ABI.
|
||||||
|
|
||||||
|
|
@ -36,7 +42,8 @@ opens a POSIX target once before its type, identity, and device-capacity
|
||||||
decisions, and benchmark cleanup is descriptor-relative on POSIX and
|
decisions, and benchmark cleanup is descriptor-relative on POSIX and
|
||||||
handle/reparse-point aware on Windows. The live-workspace symlink regression,
|
handle/reparse-point aware on Windows. The live-workspace symlink regression,
|
||||||
SDK link-target/mode regression, static path-race guards, portable raw-C1
|
SDK link-target/mode regression, static path-race guards, portable raw-C1
|
||||||
fixture, native redirected-prompt regression, and `sdk-test` CI step cover
|
fixture with Bash 3.2 unsigned-byte normalization, native redirected-prompt
|
||||||
|
and protected-DACL regressions, and `sdk-test` CI step cover
|
||||||
these boundaries. All current release paths move to 5.2.8 and require fresh
|
these boundaries. All current release paths move to 5.2.8 and require fresh
|
||||||
exact-tag hosted CI, package,
|
exact-tag hosted CI, package,
|
||||||
native-platform, source-only, checksum, OBS, and promotion evidence.
|
native-platform, source-only, checksum, OBS, and promotion evidence.
|
||||||
|
|
|
||||||
|
|
@ -228,6 +228,13 @@ publication, #6 at POSIX disk-target classification/use, and #7 at benchmark
|
||||||
workspace cleanup. The regressions and source review are project evidence, not
|
workspace cleanup. The regressions and source review are project evidence, not
|
||||||
an independent certification or a claim that exact-v5.2.8 CI has passed.
|
an independent certification or a claim that exact-v5.2.8 CI has passed.
|
||||||
|
|
||||||
|
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. A
|
||||||
|
subsequent default-branch scan is required as closure evidence.
|
||||||
|
|
||||||
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.8. Cross-build and
|
and mapped/network-drive output are not supported in 5.2.8. Cross-build and
|
||||||
|
|
|
||||||
|
|
@ -165,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"
|
||||||
|
|
@ -185,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}
|
||||||
|
|
@ -192,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) ||
|
||||||
|
|
@ -206,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) ||
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
|
#include <fcntl.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#endif
|
#endif
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
@ -55,18 +56,35 @@ static int file_matches(const char *path, const void *expected,
|
||||||
size_t expected_size) {
|
size_t expected_size) {
|
||||||
struct stat info;
|
struct stat info;
|
||||||
char observed[128];
|
char observed[128];
|
||||||
if (expected_size > sizeof(observed) || stat(path, &info) != 0 ||
|
if (expected_size > sizeof(observed))
|
||||||
info.st_size < 0 || (uint64_t)info.st_size != (uint64_t)expected_size)
|
|
||||||
return 0;
|
return 0;
|
||||||
FILE *stream = fopen(path, "rb");
|
int fd = open(path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC | O_NONBLOCK);
|
||||||
if (!stream) return 0;
|
if (fd < 0) return 0;
|
||||||
size_t got = fread(observed, 1, expected_size, stream);
|
int ok = fstat(fd, &info) == 0 && S_ISREG(info.st_mode) &&
|
||||||
int read_error = ferror(stream);
|
info.st_size >= 0 &&
|
||||||
int close_rc = fclose(stream);
|
(uint64_t)info.st_size == (uint64_t)expected_size;
|
||||||
return got == expected_size && !read_error && close_rc == 0 &&
|
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;
|
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 int private_key_save_avoids_link_targets(const zuptsdk_keypair_t *kp) {
|
||||||
static const char sentinel[] = "do not replace through a symlink\n";
|
static const char sentinel[] = "do not replace through a symlink\n";
|
||||||
char workspace[] = "/tmp/zupt-sdk-link-save.XXXXXX";
|
char workspace[] = "/tmp/zupt-sdk-link-save.XXXXXX";
|
||||||
|
|
@ -95,20 +113,22 @@ static int private_key_save_avoids_link_targets(const zuptsdk_keypair_t *kp) {
|
||||||
if (symlink(target, symlink_path) != 0 ||
|
if (symlink(target, symlink_path) != 0 ||
|
||||||
zuptsdk_keypair_save_private(kp, symlink_path) != ZUPTSDK_OK ||
|
zuptsdk_keypair_save_private(kp, symlink_path) != ZUPTSDK_OK ||
|
||||||
!file_matches(target, sentinel, sizeof(sentinel) - 1) ||
|
!file_matches(target, sentinel, sizeof(sentinel) - 1) ||
|
||||||
stat(target, &target_st) != 0 || lstat(symlink_path, &output_st) != 0 ||
|
!regular_file_info(target, &target_st) ||
|
||||||
|
!regular_file_info(symlink_path, &output_st) ||
|
||||||
(target_st.st_dev == output_st.st_dev &&
|
(target_st.st_dev == output_st.st_dev &&
|
||||||
target_st.st_ino == output_st.st_ino) ||
|
target_st.st_ino == output_st.st_ino) ||
|
||||||
!S_ISREG(output_st.st_mode) || output_st.st_size <= 0 ||
|
output_st.st_size <= 0 ||
|
||||||
(output_st.st_mode & 0777) != 0600)
|
(output_st.st_mode & 0777) != 0600)
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
|
||||||
if (link(target, hardlink_path) != 0 ||
|
if (link(target, hardlink_path) != 0 ||
|
||||||
zuptsdk_keypair_save_private(kp, hardlink_path) != ZUPTSDK_OK ||
|
zuptsdk_keypair_save_private(kp, hardlink_path) != ZUPTSDK_OK ||
|
||||||
!file_matches(target, sentinel, sizeof(sentinel) - 1) ||
|
!file_matches(target, sentinel, sizeof(sentinel) - 1) ||
|
||||||
stat(target, &target_st) != 0 || stat(hardlink_path, &output_st) != 0 ||
|
!regular_file_info(target, &target_st) ||
|
||||||
|
!regular_file_info(hardlink_path, &output_st) ||
|
||||||
(target_st.st_dev == output_st.st_dev &&
|
(target_st.st_dev == output_st.st_dev &&
|
||||||
target_st.st_ino == output_st.st_ino) ||
|
target_st.st_ino == output_st.st_ino) ||
|
||||||
!S_ISREG(output_st.st_mode) || output_st.st_size <= 0 ||
|
output_st.st_size <= 0 ||
|
||||||
(output_st.st_mode & 0777) != 0600)
|
(output_st.st_mode & 0777) != 0600)
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
|
||||||
|
|
@ -328,12 +348,16 @@ 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_priv[160];
|
||||||
char saved_pub[160];
|
char saved_pub[160];
|
||||||
|
#ifdef _WIN32
|
||||||
snprintf(saved_priv, sizeof(saved_priv), "/tmp/_zsdk_priv_%ld.key",
|
snprintf(saved_priv, sizeof(saved_priv), "/tmp/_zsdk_priv_%ld.key",
|
||||||
(long)getpid());
|
(long)getpid());
|
||||||
snprintf(saved_pub, sizeof(saved_pub), "/tmp/_zsdk_pub_%ld.key",
|
snprintf(saved_pub, sizeof(saved_pub), "/tmp/_zsdk_pub_%ld.key",
|
||||||
(long)getpid());
|
(long)getpid());
|
||||||
unlink(saved_priv);
|
unlink(saved_priv);
|
||||||
unlink(saved_pub);
|
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");
|
||||||
|
|
@ -343,6 +367,16 @@ static void test_keypair_pq(void) {
|
||||||
if (rc != ZUPTSDK_OK) { FAIL("keygen"); zuptsdk_ctx_destroy(ctx); return; }
|
if (rc != ZUPTSDK_OK) { FAIL("keygen"); zuptsdk_ctx_destroy(ctx); return; }
|
||||||
|
|
||||||
#ifndef _WIN32
|
#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)) {
|
if (!private_key_save_avoids_link_targets(kp)) {
|
||||||
FAIL("private key save followed a symlink or hardlink target");
|
FAIL("private key save followed a symlink or hardlink target");
|
||||||
goto err;
|
goto err;
|
||||||
|
|
@ -357,8 +391,8 @@ static void test_keypair_pq(void) {
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
struct stat private_st;
|
struct stat private_st;
|
||||||
struct stat public_st;
|
struct stat public_st;
|
||||||
if (stat(saved_priv, &private_st) != 0 ||
|
if (!regular_file_info(saved_priv, &private_st) ||
|
||||||
stat(saved_pub, &public_st) != 0 ||
|
!regular_file_info(saved_pub, &public_st) ||
|
||||||
(private_st.st_mode & 0777) != 0600 ||
|
(private_st.st_mode & 0777) != 0600 ||
|
||||||
(public_st.st_mode & 0777) != 0644) {
|
(public_st.st_mode & 0777) != 0644) {
|
||||||
FAIL("saved key permissions do not match the requested modes");
|
FAIL("saved key permissions do not match the requested modes");
|
||||||
|
|
@ -398,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(saved_priv);
|
if (unlink(saved_priv) != 0 || unlink(saved_pub) != 0) ok = 0;
|
||||||
unlink(saved_pub);
|
#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);
|
||||||
|
|
@ -410,6 +446,9 @@ static void test_keypair_pq(void) {
|
||||||
err:
|
err:
|
||||||
unlink(saved_priv);
|
unlink(saved_priv);
|
||||||
unlink(saved_pub);
|
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,10 +41,67 @@ 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
|
||||||
|
case $(uname -s 2>/dev/null || printf unknown) in
|
||||||
|
MINGW*|MSYS*|CYGWIN*)
|
||||||
|
if windows_private_acl "$output"; then
|
||||||
|
pass "$label has a protected current-user-only DACL under umask $mask"
|
||||||
|
else
|
||||||
|
fail "$label lacks a protected current-user-only DACL under umask $mask"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
local mode
|
local mode
|
||||||
mode=$(file_mode "$output")
|
mode=$(file_mode "$output")
|
||||||
if [[ $mode == 600 ]]; then
|
if [[ $mode == 600 ]]; then
|
||||||
|
|
@ -52,6 +109,8 @@ generate_with_mode() {
|
||||||
else
|
else
|
||||||
fail "$label mode under umask $mask is $mode, expected 600"
|
fail "$label mode under umask $mask is $mode, expected 600"
|
||||||
fi
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
else
|
else
|
||||||
fail "$label generation failed under umask $mask"
|
fail "$label generation failed under umask $mask"
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -212,6 +212,19 @@ else
|
||||||
F "SDK key save atomic publication guard is missing"
|
F "SDK key save atomic publication guard is missing"
|
||||||
fi
|
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