Merge pull request 'fix: validate delta output and integration test skips' (#207) from fix/triage-delta-validation into dev

Reviewed-on: #207
This commit was merged in pull request #207.
This commit is contained in:
2026-08-08 20:17:38 +02:00
4 changed files with 43 additions and 6 deletions
+12 -2
View File
@@ -458,7 +458,8 @@ Delta* delta_deserialize(const Data* data) {
void* delta_apply(const void* old_data, uint64_t old_size, const Delta* delta,
uint32_t block_size) {
if (!old_data || !delta)
if (!old_data || !delta || (delta->new_file_size > 0 && delta->instructions == NULL) ||
(delta->instruction_count > 0 && block_size == 0))
return NULL;
void* output = malloc((size_t)delta->new_file_size);
@@ -472,10 +473,15 @@ void* delta_apply(const void* old_data, uint64_t old_size, const Delta* delta,
for (uint32_t i = 0; i < delta->instruction_count; i++) {
if (delta->instructions[i].type == DELTA_INSTR_BLOCK_MATCH) {
uint64_t src_offset = (uint64_t)delta->instructions[i].match.block_index * block_size;
if (src_offset > UINT64_MAX - delta->instructions[i].match.block_offset) {
free(output);
return NULL;
}
src_offset += delta->instructions[i].match.block_offset;
uint32_t len = delta->instructions[i].match.length;
if (src_offset + len > old_size) {
if (src_offset > old_size || (uint64_t)len > old_size - src_offset ||
out_pos > delta->new_file_size || (uint64_t)len > delta->new_file_size - out_pos) {
free(output);
return NULL;
}
@@ -483,6 +489,10 @@ void* delta_apply(const void* old_data, uint64_t old_size, const Delta* delta,
out_pos += len;
} else {
uint32_t len = delta->instructions[i].literal.length;
if (out_pos > delta->new_file_size || (uint64_t)len > delta->new_file_size - out_pos) {
free(output);
return NULL;
}
memcpy(out + out_pos, delta->instructions[i].literal.data, len);
out_pos += len;
}
+1 -1
View File
@@ -238,7 +238,7 @@ class TestProgress:
)
assert result.returncode == 0, f"Exit {result.returncode}: {result.stderr[:100]}"
output = result.stdout + result.stderr
assert len(output) >= 0
assert output, "--progress produced no output"
class TestBandwidthLimit:
+13 -3
View File
@@ -52,9 +52,11 @@ def _check_ssh():
pass
_check_ssh()
@pytest.fixture(scope="module", autouse=True)
def setup_test_data():
_check_ssh()
if SSH_AVAILABLE:
generate_test_files(SOURCE_DIR, full=False)
clean_dir(DEST_DIR)
@@ -84,8 +86,12 @@ def _run_ssh_test(name, flags, expected_missing=None):
return make_result(name, True, duration)
@pytest.mark.skipif(not SSH_AVAILABLE, reason="SSH to localhost not available")
class TestSSHStandard:
@pytest.fixture(autouse=True)
def require_ssh(self):
if not SSH_AVAILABLE:
pytest.skip("SSH to localhost not available")
def test_standard(self):
r = _run_ssh_test("SSH (localhost)", [])
assert r["status"] == "Success", r["error"]
@@ -119,8 +125,12 @@ class TestSSHStandard:
assert r["status"] == "Success", r["error"]
@pytest.mark.skipif(not SSH_AVAILABLE, reason="SSH to localhost not available")
class TestSSHFeatures:
@pytest.fixture(autouse=True)
def require_ssh(self):
if not SSH_AVAILABLE:
pytest.skip("SSH to localhost not available")
def test_archive(self):
r = _run_ssh_test("SSH Archive (-a)", ["-a"])
assert r["status"] == "Success", r["error"]
+17
View File
@@ -323,6 +323,22 @@ static void test_large_file_delta() {
free(new_data);
}
static void test_delta_apply_rejects_output_overflow() {
uint8_t old_data[8] = {0};
uint8_t literal_data[2] = {'x', 'y'};
DeltaInstruction instruction = {
.type = DELTA_INSTR_LITERAL,
.literal = {.data = literal_data, .length = sizeof(literal_data)},
};
Delta delta = {
.new_file_size = 1,
.instruction_count = 1,
.instructions = &instruction,
};
EXPECT_TRUE(delta_apply(old_data, sizeof(old_data), &delta, 1) == NULL);
}
void test_delta() {
test_adler32_basic();
test_adler32_different_data();
@@ -338,4 +354,5 @@ void test_delta() {
test_should_attempt();
test_is_worthwhile();
test_large_file_delta();
test_delta_apply_rejects_output_overflow();
}