Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 24 additions & 24 deletions src/cli/cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ enum {
#endif
#include <errno.h> // EEXIST
#include <fcntl.h> // open, O_WRONLY, O_CREAT, O_TRUNC
#include <limits.h> // UINT_MAX
#include <stdint.h> // uintptr_t
#include <stdio.h>
#include <stdlib.h>
Expand Down Expand Up @@ -2485,12 +2486,22 @@ enum {
ZIP_STORED = 0,
ZIP_DEFLATE = 8
};
static const uint32_t ZIP_MAX_UNCOMP = 500U * 1024U * 1024U;
static const size_t ZIP_MAX_UNCOMP = 500U * 1024U * 1024U;

static uint16_t zip_read_u16le(const unsigned char *p) {
return (uint16_t)((uint16_t)p[0] | ((uint16_t)p[1] << BYTE_SHIFT));
}

static uint32_t zip_read_u32le(const unsigned char *p) {
return ((uint32_t)p[0]) | ((uint32_t)p[1] << BYTE_SHIFT) |
((uint32_t)p[2] << (BYTE_SHIFT * CLI_PAIR_LEN)) |
((uint32_t)p[3] << (BYTE_SHIFT * CLI_JSON_INDENT));
}

/* Decompress a single zip entry (stored or deflated). Returns malloc'd buffer
* or NULL on failure. *out_len receives the decompressed size. */
static unsigned char *zip_extract_entry(const unsigned char *file_data, uint16_t method,
uint32_t comp_size, uint32_t uncomp_size, int *out_len) {
size_t comp_size, size_t uncomp_size, int *out_len) {
if (method == ZIP_STORED) {
if (comp_size > ZIP_MAX_UNCOMP) {
return NULL;
Expand All @@ -2507,15 +2518,18 @@ static unsigned char *zip_extract_entry(const unsigned char *file_data, uint16_t
if (uncomp_size > ZIP_MAX_UNCOMP) {
return NULL;
}
if (comp_size > UINT_MAX || uncomp_size > UINT_MAX) {
return NULL;
}
unsigned char *out = malloc(uncomp_size);
if (!out) {
return NULL;
}
z_stream strm = {0};
strm.next_in = (unsigned char *)file_data;
strm.avail_in = comp_size;
strm.avail_in = (uInt)comp_size;
strm.next_out = out;
strm.avail_out = uncomp_size;
strm.avail_out = (uInt)uncomp_size;
if (inflateInit2(&strm, -MAX_WBITS) != Z_OK) {
free(out);
return NULL;
Expand Down Expand Up @@ -2545,28 +2559,14 @@ unsigned char *cbm_extract_binary_from_zip(const unsigned char *data, int data_l
break;
}

uint16_t method = (uint16_t)(data[pos + ZIP_OFF_METHOD] |
(data[pos + ZIP_OFF_METHOD + CLI_SKIP_ONE] << BYTE_SHIFT));
uint32_t comp_size =
(uint32_t)(data[pos + ZIP_OFF_COMP] |
(data[pos + ZIP_OFF_COMP + CLI_SKIP_ONE] << BYTE_SHIFT) |
(data[pos + ZIP_OFF_COMP + CLI_PAIR_LEN] << (BYTE_SHIFT * CLI_PAIR_LEN)) |
(data[pos + ZIP_OFF_COMP + CLI_JSON_INDENT]
<< (BYTE_SHIFT * CLI_JSON_INDENT)));
uint32_t uncomp_size =
(uint32_t)(data[pos + ZIP_OFF_UNCOMP] |
(data[pos + ZIP_OFF_UNCOMP + CLI_SKIP_ONE] << BYTE_SHIFT) |
(data[pos + ZIP_OFF_UNCOMP + CLI_PAIR_LEN] << (BYTE_SHIFT * CLI_PAIR_LEN)) |
(data[pos + ZIP_OFF_UNCOMP + CLI_JSON_INDENT]
<< (BYTE_SHIFT * CLI_JSON_INDENT)));
uint16_t name_len = (uint16_t)(data[pos + ZIP_OFF_NAMELEN] |
(data[pos + ZIP_OFF_NAMELEN + CLI_SKIP_ONE] << BYTE_SHIFT));
uint16_t extra_len =
(uint16_t)(data[pos + ZIP_OFF_EXTRALEN] |
(data[pos + ZIP_OFF_EXTRALEN + CLI_SKIP_ONE] << BYTE_SHIFT));
uint16_t method = zip_read_u16le(data + pos + ZIP_OFF_METHOD);
uint32_t comp_size = zip_read_u32le(data + pos + ZIP_OFF_COMP);
uint32_t uncomp_size = zip_read_u32le(data + pos + ZIP_OFF_UNCOMP);
uint16_t name_len = zip_read_u16le(data + pos + ZIP_OFF_NAMELEN);
uint16_t extra_len = zip_read_u16le(data + pos + ZIP_OFF_EXTRALEN);

int header_end = pos + ZIP_HDR_SZ + name_len + extra_len;
if (header_end + (int)comp_size > data_len) {
if (header_end > data_len || comp_size > (uint32_t)(data_len - header_end)) {
break;
}

Expand Down
4 changes: 2 additions & 2 deletions src/cypher/cypher.c
Original file line number Diff line number Diff line change
Expand Up @@ -2441,11 +2441,11 @@ static bool eval_condition(const cbm_condition_t *c, binding_t *b) {

/* IS NULL / IS NOT NULL */
if (strcmp(c->op, "IS NULL") == 0) {
result = (!actual || actual[0] == '\0');
result = (actual[0] == '\0');
return c->negated ? !result : result;
}
if (strcmp(c->op, "IS NOT NULL") == 0) {
result = (actual && actual[0] != '\0');
result = (actual[0] != '\0');
return c->negated ? !result : result;
}

Expand Down
30 changes: 30 additions & 0 deletions tests/test_cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "test_helpers.h"
#include <cli/cli.h>
#include <foundation/yaml.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
Expand Down Expand Up @@ -1361,6 +1362,34 @@ TEST(cli_extract_binary_from_zip_invalid) {
PASS();
}

TEST(cli_extract_binary_from_zip_truncated_large_entry) {
unsigned char zip[30 + 19] = {0};
const char *filename = "codebase-memory-mcp";
uint32_t declared_size = 65536U;
zip[0] = 0x50;
zip[1] = 0x4B;
zip[2] = 0x03;
zip[3] = 0x04;
zip[8] = 0;
zip[9] = 0;
zip[18] = (unsigned char)(declared_size & 0xFF);
zip[19] = (unsigned char)((declared_size >> 8) & 0xFF);
zip[20] = (unsigned char)((declared_size >> 16) & 0xFF);
zip[21] = (unsigned char)((declared_size >> 24) & 0xFF);
zip[22] = zip[18];
zip[23] = zip[19];
zip[24] = zip[20];
zip[25] = zip[21];
zip[26] = 19;
zip[27] = 0;
memcpy(zip + 30, filename, strlen(filename));

int out_len = 0;
unsigned char *extracted = cbm_extract_binary_from_zip(zip, sizeof(zip), &out_len);
ASSERT_NULL(extracted);
PASS();
}

/* ═══════════════════════════════════════════════════════════════════
* Skill dry-run tests
* ═══════════════════════════════════════════════════════════════════ */
Expand Down Expand Up @@ -2981,6 +3010,7 @@ SUITE(cli) {
RUN_TEST(cli_extract_binary_from_zip_not_found);
RUN_TEST(cli_extract_binary_from_zip_path_traversal);
RUN_TEST(cli_extract_binary_from_zip_invalid);
RUN_TEST(cli_extract_binary_from_zip_truncated_large_entry);

/* Dry-run lifecycle (2 tests) */
RUN_TEST(cli_install_dry_run);
Expand Down
Loading