mirror of
https://github.com/git/git.git
synced 2026-08-07 00:22:05 +00:00
Restart points encode records in a given block that do not use prefix
compression and that can thus immediately be seeked to. These offsets
are encoded in the restart table, where each offset needs to point at
one of the records of the block. We do not verify this though, so a
bogus restart offset may cause an out-of-bounds read:
==1472280==ERROR: AddressSanitizer: SEGV on unknown address 0x7d8ff7de5f7f (pc 0x55555599502b bp 0x7fffffff4df0 sp 0x7fffffff4d40 T0)
==1472280==The signal is caused by a READ memory access.
#0 0x55555599502b in get_var_int ./git/build/../reftable/record.c:30:6
#1 0x555555995c2a in reftable_decode_keylen ./git/build/../reftable/record.c:177:6
#2 0x55555598e85c in restart_needle_less ./git/build/../reftable/block.c:455:6
#3 0x55555598895f in binsearch ./git/build/../reftable/basics.c:175:9
#4 0x55555598e189 in block_iter_seek_key ./git/build/../reftable/block.c:543:6
#5 0x555555814aee in test_reftable_block__corrupt_restart_offset ./git/build/../t/unit-tests/u-reftable-block.c:636:20
#6 0x5555557f684e in clar_run_test ./git/build/../t/unit-tests/clar/clar.c:335:3
#7 0x5555557f2e69 in clar_run_suite ./git/build/../t/unit-tests/clar/clar.c:431:3
#8 0x5555557f2882 in clar_test_run ./git/build/../t/unit-tests/clar/clar.c:636:4
#9 0x5555557f375f in clar_test ./git/build/../t/unit-tests/clar/clar.c:687:11
#10 0x5555557fa49d in cmd_main ./git/build/../t/unit-tests/unit-test.c:62:8
#11 0x55555584c25a in main ./git/build/../common-main.c:9:11
#12 0x7ffff7a2b284 in __libc_start_call_main (/nix/store/57iz36553175g3178pvxjij8z5rcsd4n-glibc-2.42-61/lib/libc.so.6+0x2b284) (BuildId: 8ae0b698f2d4e727f569f64bb166e08ae30bd077)
#13 0x7ffff7a2b337 in __libc_start_main@GLIBC_2.2.5 (/nix/store/57iz36553175g3178pvxjij8z5rcsd4n-glibc-2.42-61/lib/libc.so.6+0x2b337) (BuildId: 8ae0b698f2d4e727f569f64bb166e08ae30bd077)
#14 0x555555694c24 in _start (./git/build/t/unit-tests+0x140c24)
==1472280==Register values:
rax = 0x00007d8ff7de5f7f rbx = 0x00007fffffff4e00 rcx = 0x00007d8ff7de5f80 rdx = 0x00007bfff5b6af60
rdi = 0x00007bfff5b6af40 rsi = 0x00007bfff592dfa0 rbp = 0x00007fffffff4df0 rsp = 0x00007fffffff4d40
r8 = 0x00000000ff00002b r9 = 0x00007d8ff7de5f7f r10 = 0x00000f7ffeb25bf0 r11 = 0xf3f30000f1f1f1f1
r12 = 0x00007fffffff58f8 r13 = 0x0000000000000001 r14 = 0x00007ffff7ffd000 r15 = 0x0000555556055fd0
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV ./git/build/../reftable/record.c:30:6 in get_var_int
Guard against such restart offsets and signal an error to the caller via
`args.error`.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
610 lines
17 KiB
C
610 lines
17 KiB
C
/*
|
|
Copyright 2020 Google LLC
|
|
|
|
Use of this source code is governed by a BSD-style
|
|
license that can be found in the LICENSE file or at
|
|
https://developers.google.com/open-source/licenses/bsd
|
|
*/
|
|
|
|
#include "unit-test.h"
|
|
#include "lib-reftable.h"
|
|
#include "reftable/block.h"
|
|
#include "reftable/blocksource.h"
|
|
#include "reftable/constants.h"
|
|
#include "reftable/reftable-error.h"
|
|
#include "strbuf.h"
|
|
|
|
static int cl_reftable_write_block(struct reftable_buf *buf,
|
|
uint8_t block_type,
|
|
struct reftable_record *recs,
|
|
size_t nrecs)
|
|
{
|
|
struct block_writer writer = {
|
|
.last_key = REFTABLE_BUF_INIT,
|
|
};
|
|
uint8_t block[1024];
|
|
int block_end;
|
|
|
|
cl_must_pass(block_writer_init(&writer, block_type, block, 1024,
|
|
0, hash_size(REFTABLE_HASH_SHA1)));
|
|
for (size_t i = 0; i < nrecs; i++)
|
|
cl_must_pass(block_writer_add(&writer, &recs[i]));
|
|
|
|
block_end = block_writer_finish(&writer);
|
|
cl_assert(block_end > 0);
|
|
|
|
cl_must_pass(reftable_buf_add(buf, block, block_end));
|
|
|
|
block_writer_release(&writer);
|
|
return block_end;
|
|
}
|
|
|
|
void test_reftable_block__read_write(void)
|
|
{
|
|
const int header_off = 21; /* random */
|
|
struct reftable_record recs[30];
|
|
const size_t N = ARRAY_SIZE(recs);
|
|
const size_t block_size = 1024;
|
|
struct reftable_block_source source = { 0 };
|
|
struct block_writer bw = {
|
|
.last_key = REFTABLE_BUF_INIT,
|
|
};
|
|
struct reftable_record rec = {
|
|
.type = REFTABLE_BLOCK_TYPE_REF,
|
|
};
|
|
size_t i = 0;
|
|
int ret;
|
|
struct reftable_block block = { 0 };
|
|
struct block_iter it = BLOCK_ITER_INIT;
|
|
struct reftable_buf want = REFTABLE_BUF_INIT;
|
|
struct reftable_buf block_data = REFTABLE_BUF_INIT;
|
|
|
|
REFTABLE_CALLOC_ARRAY(block_data.buf, block_size);
|
|
cl_assert(block_data.buf != NULL);
|
|
block_data.len = block_size;
|
|
|
|
ret = block_writer_init(&bw, REFTABLE_BLOCK_TYPE_REF,
|
|
(uint8_t *) block_data.buf, block_size,
|
|
header_off, hash_size(REFTABLE_HASH_SHA1));
|
|
cl_assert(!ret);
|
|
|
|
rec.u.ref.refname = (char *) "";
|
|
rec.u.ref.value_type = REFTABLE_REF_DELETION;
|
|
ret = block_writer_add(&bw, &rec);
|
|
cl_assert_equal_i(ret, REFTABLE_API_ERROR);
|
|
|
|
for (i = 0; i < N; i++) {
|
|
rec.u.ref.refname = xstrfmt("branch%02"PRIuMAX, (uintmax_t)i);
|
|
rec.u.ref.value_type = REFTABLE_REF_VAL1;
|
|
memset(rec.u.ref.value.val1, i, REFTABLE_HASH_SIZE_SHA1);
|
|
|
|
recs[i] = rec;
|
|
ret = block_writer_add(&bw, &rec);
|
|
rec.u.ref.refname = NULL;
|
|
rec.u.ref.value_type = REFTABLE_REF_DELETION;
|
|
cl_assert_equal_i(ret, 0);
|
|
}
|
|
|
|
ret = block_writer_finish(&bw);
|
|
cl_assert(ret > 0);
|
|
|
|
block_writer_release(&bw);
|
|
|
|
block_source_from_buf(&source ,&block_data);
|
|
reftable_block_init(&block, &source, 0, header_off, block_size,
|
|
REFTABLE_HASH_SIZE_SHA1, REFTABLE_BLOCK_TYPE_REF);
|
|
|
|
block_iter_init(&it, &block);
|
|
|
|
for (i = 0; ; i++) {
|
|
ret = block_iter_next(&it, &rec);
|
|
cl_assert(ret >= 0);
|
|
if (ret > 0) {
|
|
cl_assert_equal_i(i, N);
|
|
break;
|
|
}
|
|
cl_assert_equal_i(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1), 1);
|
|
}
|
|
|
|
for (i = 0; i < N; i++) {
|
|
reftable_record_key(&recs[i], &want);
|
|
|
|
ret = block_iter_seek_key(&it, &want);
|
|
cl_assert_equal_i(ret, 0);
|
|
|
|
ret = block_iter_next(&it, &rec);
|
|
cl_assert_equal_i(ret, 0);
|
|
|
|
cl_assert_equal_i(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1), 1);
|
|
|
|
want.len--;
|
|
ret = block_iter_seek_key(&it, &want);
|
|
cl_assert_equal_i(ret, 0);
|
|
|
|
ret = block_iter_next(&it, &rec);
|
|
cl_assert_equal_i(ret, 0);
|
|
cl_assert_equal_i(reftable_record_equal(&recs[10 * (i / 10)], &rec, REFTABLE_HASH_SIZE_SHA1), 1);
|
|
}
|
|
|
|
reftable_block_release(&block);
|
|
block_iter_close(&it);
|
|
reftable_record_release(&rec);
|
|
reftable_buf_release(&want);
|
|
reftable_buf_release(&block_data);
|
|
for (i = 0; i < N; i++)
|
|
reftable_record_release(&recs[i]);
|
|
}
|
|
|
|
void test_reftable_block__log_read_write(void)
|
|
{
|
|
const int header_off = 21;
|
|
struct reftable_record recs[30];
|
|
const size_t N = ARRAY_SIZE(recs);
|
|
const size_t block_size = 2048;
|
|
struct reftable_block_source source = { 0 };
|
|
struct block_writer bw = {
|
|
.last_key = REFTABLE_BUF_INIT,
|
|
};
|
|
struct reftable_record rec = {
|
|
.type = REFTABLE_BLOCK_TYPE_LOG,
|
|
};
|
|
size_t i = 0;
|
|
int ret;
|
|
struct reftable_block block = { 0 };
|
|
struct block_iter it = BLOCK_ITER_INIT;
|
|
struct reftable_buf want = REFTABLE_BUF_INIT;
|
|
struct reftable_buf block_data = REFTABLE_BUF_INIT;
|
|
|
|
REFTABLE_CALLOC_ARRAY(block_data.buf, block_size);
|
|
cl_assert(block_data.buf != NULL);
|
|
block_data.len = block_size;
|
|
|
|
ret = block_writer_init(&bw, REFTABLE_BLOCK_TYPE_LOG, (uint8_t *) block_data.buf, block_size,
|
|
header_off, hash_size(REFTABLE_HASH_SHA1));
|
|
cl_assert(!ret);
|
|
|
|
for (i = 0; i < N; i++) {
|
|
rec.u.log.refname = xstrfmt("branch%02"PRIuMAX , (uintmax_t)i);
|
|
rec.u.log.update_index = i;
|
|
rec.u.log.value_type = REFTABLE_LOG_UPDATE;
|
|
|
|
recs[i] = rec;
|
|
ret = block_writer_add(&bw, &rec);
|
|
rec.u.log.refname = NULL;
|
|
rec.u.log.value_type = REFTABLE_LOG_DELETION;
|
|
cl_assert_equal_i(ret, 0);
|
|
}
|
|
|
|
ret = block_writer_finish(&bw);
|
|
cl_assert(ret > 0);
|
|
|
|
block_writer_release(&bw);
|
|
|
|
block_source_from_buf(&source, &block_data);
|
|
reftable_block_init(&block, &source, 0, header_off, block_size,
|
|
REFTABLE_HASH_SIZE_SHA1, REFTABLE_BLOCK_TYPE_LOG);
|
|
|
|
block_iter_init(&it, &block);
|
|
|
|
for (i = 0; ; i++) {
|
|
ret = block_iter_next(&it, &rec);
|
|
cl_assert(ret >= 0);
|
|
if (ret > 0) {
|
|
cl_assert_equal_i(i, N);
|
|
break;
|
|
}
|
|
cl_assert_equal_i(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1), 1);
|
|
}
|
|
|
|
for (i = 0; i < N; i++) {
|
|
reftable_buf_reset(&want);
|
|
cl_assert(reftable_buf_addstr(&want, recs[i].u.log.refname) == 0);
|
|
|
|
ret = block_iter_seek_key(&it, &want);
|
|
cl_assert_equal_i(ret, 0);
|
|
|
|
ret = block_iter_next(&it, &rec);
|
|
cl_assert_equal_i(ret, 0);
|
|
|
|
cl_assert_equal_i(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1), 1);
|
|
|
|
want.len--;
|
|
ret = block_iter_seek_key(&it, &want);
|
|
cl_assert_equal_i(ret, 0);
|
|
|
|
ret = block_iter_next(&it, &rec);
|
|
cl_assert_equal_i(ret, 0);
|
|
cl_assert_equal_i(reftable_record_equal(&recs[10 * (i / 10)], &rec, REFTABLE_HASH_SIZE_SHA1), 1);
|
|
}
|
|
|
|
reftable_block_release(&block);
|
|
block_iter_close(&it);
|
|
reftable_record_release(&rec);
|
|
reftable_buf_release(&want);
|
|
reftable_buf_release(&block_data);
|
|
for (i = 0; i < N; i++)
|
|
reftable_record_release(&recs[i]);
|
|
}
|
|
|
|
void test_reftable_block__obj_read_write(void)
|
|
{
|
|
const int header_off = 21;
|
|
struct reftable_record recs[30];
|
|
const size_t N = ARRAY_SIZE(recs);
|
|
const size_t block_size = 1024;
|
|
struct reftable_block_source source = { 0 };
|
|
struct block_writer bw = {
|
|
.last_key = REFTABLE_BUF_INIT,
|
|
};
|
|
struct reftable_record rec = {
|
|
.type = REFTABLE_BLOCK_TYPE_OBJ,
|
|
};
|
|
size_t i = 0;
|
|
int ret;
|
|
struct reftable_block block = { 0 };
|
|
struct block_iter it = BLOCK_ITER_INIT;
|
|
struct reftable_buf want = REFTABLE_BUF_INIT;
|
|
struct reftable_buf block_data = REFTABLE_BUF_INIT;
|
|
|
|
REFTABLE_CALLOC_ARRAY(block_data.buf, block_size);
|
|
cl_assert(block_data.buf != NULL);
|
|
block_data.len = block_size;
|
|
|
|
ret = block_writer_init(&bw, REFTABLE_BLOCK_TYPE_OBJ, (uint8_t *) block_data.buf, block_size,
|
|
header_off, hash_size(REFTABLE_HASH_SHA1));
|
|
cl_assert(!ret);
|
|
|
|
for (i = 0; i < N; i++) {
|
|
uint8_t bytes[] = { i, i + 1, i + 2, i + 3, i + 5 }, *allocated;
|
|
DUP_ARRAY(allocated, bytes, ARRAY_SIZE(bytes));
|
|
|
|
rec.u.obj.hash_prefix = allocated;
|
|
rec.u.obj.hash_prefix_len = 5;
|
|
|
|
recs[i] = rec;
|
|
ret = block_writer_add(&bw, &rec);
|
|
rec.u.obj.hash_prefix = NULL;
|
|
rec.u.obj.hash_prefix_len = 0;
|
|
cl_assert_equal_i(ret, 0);
|
|
}
|
|
|
|
ret = block_writer_finish(&bw);
|
|
cl_assert(ret > 0);
|
|
|
|
block_writer_release(&bw);
|
|
|
|
block_source_from_buf(&source, &block_data);
|
|
reftable_block_init(&block, &source, 0, header_off, block_size,
|
|
REFTABLE_HASH_SIZE_SHA1, REFTABLE_BLOCK_TYPE_OBJ);
|
|
|
|
block_iter_init(&it, &block);
|
|
|
|
for (i = 0; ; i++) {
|
|
ret = block_iter_next(&it, &rec);
|
|
cl_assert(ret >= 0);
|
|
if (ret > 0) {
|
|
cl_assert_equal_i(i, N);
|
|
break;
|
|
}
|
|
cl_assert_equal_i(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1), 1);
|
|
}
|
|
|
|
for (i = 0; i < N; i++) {
|
|
reftable_record_key(&recs[i], &want);
|
|
|
|
ret = block_iter_seek_key(&it, &want);
|
|
cl_assert_equal_i(ret, 0);
|
|
|
|
ret = block_iter_next(&it, &rec);
|
|
cl_assert_equal_i(ret, 0);
|
|
|
|
cl_assert_equal_i(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1), 1);
|
|
}
|
|
|
|
reftable_block_release(&block);
|
|
block_iter_close(&it);
|
|
reftable_record_release(&rec);
|
|
reftable_buf_release(&want);
|
|
reftable_buf_release(&block_data);
|
|
for (i = 0; i < N; i++)
|
|
reftable_record_release(&recs[i]);
|
|
}
|
|
|
|
void test_reftable_block__ref_read_write(void)
|
|
{
|
|
const int header_off = 21;
|
|
struct reftable_record recs[30];
|
|
const size_t N = ARRAY_SIZE(recs);
|
|
const size_t block_size = 1024;
|
|
struct reftable_block_source source = { 0 };
|
|
struct block_writer bw = {
|
|
.last_key = REFTABLE_BUF_INIT,
|
|
};
|
|
struct reftable_record rec = {
|
|
.type = REFTABLE_BLOCK_TYPE_INDEX,
|
|
.u.idx.last_key = REFTABLE_BUF_INIT,
|
|
};
|
|
size_t i = 0;
|
|
int ret;
|
|
struct reftable_block block = { 0 };
|
|
struct block_iter it = BLOCK_ITER_INIT;
|
|
struct reftable_buf want = REFTABLE_BUF_INIT;
|
|
struct reftable_buf block_data = REFTABLE_BUF_INIT;
|
|
|
|
REFTABLE_CALLOC_ARRAY(block_data.buf, block_size);
|
|
cl_assert(block_data.buf != NULL);
|
|
block_data.len = block_size;
|
|
|
|
ret = block_writer_init(&bw, REFTABLE_BLOCK_TYPE_INDEX, (uint8_t *) block_data.buf, block_size,
|
|
header_off, hash_size(REFTABLE_HASH_SHA1));
|
|
cl_assert(!ret);
|
|
|
|
for (i = 0; i < N; i++) {
|
|
char buf[128];
|
|
|
|
snprintf(buf, sizeof(buf), "branch%02"PRIuMAX, (uintmax_t)i);
|
|
|
|
reftable_buf_init(&recs[i].u.idx.last_key);
|
|
recs[i].type = REFTABLE_BLOCK_TYPE_INDEX;
|
|
cl_assert(!reftable_buf_addstr(&recs[i].u.idx.last_key, buf));
|
|
recs[i].u.idx.offset = i;
|
|
|
|
ret = block_writer_add(&bw, &recs[i]);
|
|
cl_assert_equal_i(ret, 0);
|
|
}
|
|
|
|
ret = block_writer_finish(&bw);
|
|
cl_assert(ret > 0);
|
|
|
|
block_writer_release(&bw);
|
|
|
|
block_source_from_buf(&source, &block_data);
|
|
reftable_block_init(&block, &source, 0, header_off, block_size,
|
|
REFTABLE_HASH_SIZE_SHA1, REFTABLE_BLOCK_TYPE_INDEX);
|
|
|
|
block_iter_init(&it, &block);
|
|
|
|
for (i = 0; ; i++) {
|
|
ret = block_iter_next(&it, &rec);
|
|
cl_assert(ret >= 0);
|
|
if (ret > 0) {
|
|
cl_assert_equal_i(i, N);
|
|
break;
|
|
}
|
|
cl_assert_equal_i(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1), 1);
|
|
}
|
|
|
|
for (i = 0; i < N; i++) {
|
|
reftable_record_key(&recs[i], &want);
|
|
|
|
ret = block_iter_seek_key(&it, &want);
|
|
cl_assert_equal_i(ret, 0);
|
|
|
|
ret = block_iter_next(&it, &rec);
|
|
cl_assert_equal_i(ret, 0);
|
|
|
|
cl_assert_equal_i(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1), 1);
|
|
|
|
want.len--;
|
|
ret = block_iter_seek_key(&it, &want);
|
|
cl_assert_equal_i(ret, 0);
|
|
|
|
ret = block_iter_next(&it, &rec);
|
|
cl_assert_equal_i(ret, 0);
|
|
cl_assert_equal_i(reftable_record_equal(&recs[10 * (i / 10)], &rec, REFTABLE_HASH_SIZE_SHA1), 1);
|
|
}
|
|
|
|
reftable_block_release(&block);
|
|
block_iter_close(&it);
|
|
reftable_record_release(&rec);
|
|
reftable_buf_release(&want);
|
|
reftable_buf_release(&block_data);
|
|
for (i = 0; i < N; i++)
|
|
reftable_record_release(&recs[i]);
|
|
}
|
|
|
|
void test_reftable_block__iterator(void)
|
|
{
|
|
struct reftable_block_source source = { 0 };
|
|
struct reftable_record expected_refs[20];
|
|
struct reftable_ref_record ref = { 0 };
|
|
struct reftable_iterator it = { 0 };
|
|
struct reftable_block block = { 0 };
|
|
struct reftable_buf data = REFTABLE_BUF_INIT;
|
|
int err;
|
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(expected_refs); i++) {
|
|
expected_refs[i] = (struct reftable_record) {
|
|
.type = REFTABLE_BLOCK_TYPE_REF,
|
|
.u.ref = {
|
|
.value_type = REFTABLE_REF_VAL1,
|
|
.refname = xstrfmt("refs/heads/branch-%02"PRIuMAX, (uintmax_t)i),
|
|
},
|
|
};
|
|
memset(expected_refs[i].u.ref.value.val1, i, REFTABLE_HASH_SIZE_SHA1);
|
|
}
|
|
|
|
cl_reftable_write_block(&data, REFTABLE_BLOCK_TYPE_REF,
|
|
expected_refs, ARRAY_SIZE(expected_refs));
|
|
|
|
block_source_from_buf(&source, &data);
|
|
reftable_block_init(&block, &source, 0, 0, data.len,
|
|
REFTABLE_HASH_SIZE_SHA1, REFTABLE_BLOCK_TYPE_REF);
|
|
|
|
err = reftable_block_init_iterator(&block, &it);
|
|
cl_assert_equal_i(err, 0);
|
|
|
|
for (size_t i = 0; ; i++) {
|
|
err = reftable_iterator_next_ref(&it, &ref);
|
|
if (err > 0) {
|
|
cl_assert_equal_i(i, ARRAY_SIZE(expected_refs));
|
|
break;
|
|
}
|
|
cl_assert_equal_i(err, 0);
|
|
|
|
cl_assert(reftable_ref_record_equal(&ref,
|
|
&expected_refs[i].u.ref, REFTABLE_HASH_SIZE_SHA1));
|
|
}
|
|
|
|
err = reftable_iterator_seek_ref(&it, "refs/heads/does-not-exist");
|
|
cl_assert_equal_i(err, 0);
|
|
err = reftable_iterator_next_ref(&it, &ref);
|
|
cl_assert_equal_i(err, 1);
|
|
|
|
err = reftable_iterator_seek_ref(&it, "refs/heads/branch-13");
|
|
cl_assert_equal_i(err, 0);
|
|
err = reftable_iterator_next_ref(&it, &ref);
|
|
cl_assert_equal_i(err, 0);
|
|
cl_assert(reftable_ref_record_equal(&ref,
|
|
&expected_refs[13].u.ref,REFTABLE_HASH_SIZE_SHA1));
|
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(expected_refs); i++)
|
|
reftable_free(expected_refs[i].u.ref.refname);
|
|
reftable_ref_record_release(&ref);
|
|
reftable_iterator_destroy(&it);
|
|
reftable_block_release(&block);
|
|
reftable_buf_release(&data);
|
|
}
|
|
|
|
void test_reftable_block__corrupt_log_block_size(void)
|
|
{
|
|
struct reftable_block_source source = { 0 };
|
|
struct reftable_record rec = {
|
|
.type = REFTABLE_BLOCK_TYPE_LOG,
|
|
.u.log = {
|
|
.refname = (char *) "refs/heads/main",
|
|
.update_index = 1,
|
|
.value_type = REFTABLE_LOG_UPDATE,
|
|
},
|
|
};
|
|
struct reftable_block block = { 0 };
|
|
struct reftable_buf data = REFTABLE_BUF_INIT;
|
|
|
|
cl_reftable_write_block(&data, REFTABLE_BLOCK_TYPE_LOG, &rec, 1);
|
|
|
|
/*
|
|
* Log blocks store their inflated size as a big-endian 24-bit integer
|
|
* right after the one-byte block type. Rewrite it to claim a size that
|
|
* is smaller than the block header.
|
|
*/
|
|
reftable_put_be24((uint8_t *) data.buf + 1, 1);
|
|
|
|
block_source_from_buf(&source, &data);
|
|
cl_assert_equal_i(reftable_block_init(&block, &source, 0, 0, data.len,
|
|
REFTABLE_HASH_SIZE_SHA1, REFTABLE_BLOCK_TYPE_LOG),
|
|
REFTABLE_FORMAT_ERROR);
|
|
|
|
reftable_block_release(&block);
|
|
reftable_buf_release(&data);
|
|
}
|
|
|
|
void test_reftable_block__corrupt_block_size(void)
|
|
{
|
|
struct reftable_block_source source = { 0 };
|
|
struct reftable_record rec = {
|
|
.type = REFTABLE_BLOCK_TYPE_REF,
|
|
.u.ref = {
|
|
.value_type = REFTABLE_REF_VAL1,
|
|
.refname = (char *) "refs/heads/main",
|
|
},
|
|
};
|
|
struct reftable_block block = { 0 };
|
|
struct reftable_buf data = REFTABLE_BUF_INIT;
|
|
uint32_t block_size;
|
|
unsigned char *p;
|
|
|
|
cl_reftable_write_block(&data, REFTABLE_BLOCK_TYPE_REF, &rec, 1);
|
|
|
|
/*
|
|
* The block size is stored as a big-endian 24-bit integer right after
|
|
* the one-byte block type at the start of the block. Corrupt it to
|
|
* claim a size that is larger than the data we actually have. Reading
|
|
* the restart count and restart table relative to such a bogus block
|
|
* size must not access out-of-bounds memory.
|
|
*/
|
|
p = (unsigned char *) data.buf + 1;
|
|
block_size = reftable_get_be24(p);
|
|
cl_assert_equal_i(block_size, 47);
|
|
reftable_put_be24(p, block_size + 1);
|
|
|
|
block_source_from_buf(&source, &data);
|
|
cl_assert_equal_i(reftable_block_init(&block, &source, 0, 0, data.len,
|
|
REFTABLE_HASH_SIZE_SHA1, REFTABLE_BLOCK_TYPE_REF),
|
|
REFTABLE_FORMAT_ERROR);
|
|
|
|
reftable_block_release(&block);
|
|
reftable_buf_release(&data);
|
|
}
|
|
|
|
void test_reftable_block__corrupt_restart_count(void)
|
|
{
|
|
struct reftable_block_source source = { 0 };
|
|
struct reftable_record rec = {
|
|
.type = REFTABLE_BLOCK_TYPE_REF,
|
|
.u.ref = {
|
|
.value_type = REFTABLE_REF_VAL1,
|
|
.refname = (char *) "refs/heads/main",
|
|
},
|
|
};
|
|
struct reftable_block block = { 0 };
|
|
struct reftable_buf data = REFTABLE_BUF_INIT;
|
|
int block_size;
|
|
|
|
block_size = cl_reftable_write_block(&data, REFTABLE_BLOCK_TYPE_REF, &rec, 1);
|
|
|
|
/*
|
|
* Corrupt the restart count to claim a bogus number of restart points.
|
|
* Note that this would only cause us to perform an out-of-bounds
|
|
* access when seeking into the block, but we want to refuse such a
|
|
* block outright.
|
|
*/
|
|
reftable_put_be16((uint8_t *) data.buf + block_size - 2, 0xffff);
|
|
|
|
block_source_from_buf(&source, &data);
|
|
cl_assert_equal_i(reftable_block_init(&block, &source, 0, 0, data.len,
|
|
REFTABLE_HASH_SIZE_SHA1, REFTABLE_BLOCK_TYPE_REF),
|
|
REFTABLE_FORMAT_ERROR);
|
|
|
|
reftable_block_release(&block);
|
|
reftable_buf_release(&data);
|
|
}
|
|
|
|
void test_reftable_block__corrupt_restart_offset(void)
|
|
{
|
|
struct reftable_block_source source = { 0 };
|
|
struct reftable_record rec = {
|
|
.type = REFTABLE_BLOCK_TYPE_REF,
|
|
.u.ref = {
|
|
.value_type = REFTABLE_REF_VAL1,
|
|
.refname = (char *) "refs/heads/main",
|
|
},
|
|
};
|
|
struct reftable_block block = { 0 };
|
|
struct block_iter it = BLOCK_ITER_INIT;
|
|
struct reftable_buf want = REFTABLE_BUF_INIT;
|
|
struct reftable_buf data = REFTABLE_BUF_INIT;
|
|
|
|
cl_reftable_write_block(&data, REFTABLE_BLOCK_TYPE_REF, &rec, 1);
|
|
|
|
block_source_from_buf(&source, &data);
|
|
cl_must_pass(reftable_block_init(&block, &source, 0, 0, data.len,
|
|
REFTABLE_HASH_SIZE_SHA1, REFTABLE_BLOCK_TYPE_REF));
|
|
|
|
/*
|
|
* Corrupt the first restart offset, stored as a big-endian 24-bit
|
|
* integer at the start of the restart table, to point past the end of
|
|
* the records section. Seeking such a block must fail gracefully.
|
|
*/
|
|
reftable_put_be24((uint8_t *) block.block_data.data + block.restart_off,
|
|
0xffffff);
|
|
|
|
block_iter_init(&it, &block);
|
|
cl_must_pass(reftable_buf_addstr(&want, "refs/heads/main"));
|
|
cl_assert_equal_i(block_iter_seek_key(&it, &want), REFTABLE_FORMAT_ERROR);
|
|
|
|
reftable_buf_release(&want);
|
|
block_iter_close(&it);
|
|
reftable_block_release(&block);
|
|
reftable_buf_release(&data);
|
|
}
|