cli: fix self-update checksum computation and fail closed

The macOS digest path invoked `shasum -a CBM_SZ_256` — a literal string
(not a macro; string literals do not expand) naming an invalid algorithm, so
the digest always failed on macOS, and the self-update path then treated a
could-not-verify result as non-fatal and installed the download regardless.
Use the correct algorithm, and install only a positively-verified download:
a mismatch, a missing checksum entry, or an unavailable hash tool now all
abort. Adds a known-vector digest regression test.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
Martin Vogel
2026-07-07 21:31:23 +02:00
parent 72e6a14adb
commit 4bbcfec503
2 changed files with 39 additions and 7 deletions
+12 -7
View File
@@ -2968,15 +2968,16 @@ static bool prompt_yn(const char *question) {
/* Minimum line length in checksums.txt: CBM_SZ_64 hex + 2 spaces + 1 char filename */
#define CHECKSUM_LINE_MIN (SHA256_HEX_LEN + 2)
/* Compute SHA-CBM_SZ_256 of a file using platform tools (sha256sum/shasum).
* Writes CBM_SZ_64-char hex digest + NUL to out. Returns 0 on success. */
static int sha256_file(const char *path, char *out, size_t out_size) {
/* Compute the SHA-256 of a file using platform tools (sha256sum/shasum).
* Writes a 64-char hex digest + NUL to out. Returns 0 on success. Not static:
* exercised directly by the self-update checksum regression test. */
int cbm_cli_sha256_file(const char *path, char *out, size_t out_size) {
if (out_size < SHA256_BUF_SIZE) {
return CLI_ERR;
}
char cmd[CLI_BUF_1K];
#ifdef __APPLE__
snprintf(cmd, sizeof(cmd), "shasum -a CBM_SZ_256 '%s' 2>/dev/null", path);
snprintf(cmd, sizeof(cmd), "shasum -a 256 '%s' 2>/dev/null", path);
#else
snprintf(cmd, sizeof(cmd), "sha256sum '%s' 2>/dev/null", path);
#endif
@@ -3107,8 +3108,8 @@ static int verify_download_checksum(const char *archive_path, const char *archiv
}
char actual[SHA256_BUF_SIZE] = {0};
if (sha256_file(archive_path, actual, sizeof(actual)) != 0) {
(void)fprintf(stderr, "warning: sha256sum/shasum not available — skipping verification\n");
if (cbm_cli_sha256_file(archive_path, actual, sizeof(actual)) != 0) {
(void)fprintf(stderr, "error: could not compute checksum (sha256 tool unavailable)\n");
return CLI_ERR;
}
@@ -4250,8 +4251,12 @@ static int download_verify_install(const char *url, const char *ext, const char
const char *portable = (strcmp(os, "linux") == 0) ? "-portable" : "";
snprintf(archive_name, sizeof(archive_name), "codebase-memory-mcp-%s%s-%s%s.%s",
want_ui ? "ui-" : "", os, arch, portable, ext);
/* Fail closed: install only a positively-verified download. A mismatch,
* a missing checksum entry, or an unavailable hash tool (crc != 0) all
* abort rather than install an unverified binary. */
int crc = verify_download_checksum(tmp_archive, archive_name);
if (crc == CLI_TRUE) {
if (crc != 0) {
(void)fprintf(stderr, "error: refusing to install an unverified download\n");
cbm_unlink(tmp_archive);
return CLI_TRUE;
}
+27
View File
@@ -3010,11 +3010,38 @@ TEST(cli_print_tool_help_issue680) {
PASS();
}
/* The self-update path verifies a downloaded archive against a published
* checksum. That check is only meaningful if the digest is actually computed —
* a broken hash command (it once invoked `shasum -a CBM_SZ_256`, an invalid
* algorithm, from a bad macro rename inside the shell string) makes every
* digest fail, and the caller then falls through and installs unverified.
* Guard the digest itself against a known vector. */
extern int cbm_cli_sha256_file(const char *path, char *out, size_t out_size);
TEST(cli_sha256_file_matches_known_vector) {
char path[512];
snprintf(path, sizeof(path), "%s/cbm_sha_XXXXXX", cbm_tmpdir());
int fd = cbm_mkstemp(path);
ASSERT_TRUE(fd >= 0);
FILE *fp = fdopen(fd, "wb");
ASSERT_NOT_NULL(fp);
fwrite("abc", 1, 3, fp); /* sha256("abc") is a NIST test vector */
fclose(fp);
char digest[128] = {0};
int rc = cbm_cli_sha256_file(path, digest, sizeof(digest));
remove(path);
ASSERT_EQ(rc, 0);
ASSERT_STR_EQ(digest, "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
PASS();
}
/* ═══════════════════════════════════════════════════════════════════
* Suite definition
* ═══════════════════════════════════════════════════════════════════ */
SUITE(cli) {
RUN_TEST(cli_sha256_file_matches_known_vector);
/* Version (2 tests — selfupdate_test.go) */
RUN_TEST(cli_compare_versions);
RUN_TEST(cli_version_get_set);