Message ID | 20241206-424-reftable-writer-add-check-for-limits-v2-1-82ca350b10be@gmail.com (mailing list archive) |
---|---|
State | Accepted |
Commit | 49c6b912e2f4c49784d471f8c9364077c423dbf5 |
Headers | show |
Series | [v2] reftable/writer: ensure valid range for log's update_index | expand |
Karthik Nayak <karthik.188@gmail.com> writes: > The corresponding check for reflogs in `reftable_writer_add_log` is > however missing. Add a similar check, but only check for the upper > limit. This is because reflogs are treated a bit differently than refs. > Each reflog entry in reftable has an associated update_index and we also > allow expiring entries in the middle, which is done by simply writing a > new reflog entry with the same update_index. This means, writing reflog > entries with update_index lesser than the writer's update_index is an > expected scenario. > > Add a new unit test to check for the limits and fix some of the existing > tests, which were setting arbitrary values for the update_index by > ensuring they stay within the now checked limits. Interesting. I am a little curious how this was found. As long as the other parts of the system (i.e. the callers) are not buggy, the update pointer will stay within the range, and that is why I do not think I can write an end-to-end test to trigger an existing bug that would be caught by this "fix". Fixing the existing unit tests that feed a wrong input and expect some right outcome is good, but would it be a good to also have a new unit test that validates that such an incorrect precondition for calling is caught and the caller gets the expected REFTABLE_API_ERROR as a result, I wonder? Being able to trigger a condition that is harder to do with the end-to-end test is one good thing about having unit test framework, no? Will queue. Thanks.
Junio C Hamano <gitster@pobox.com> writes: > Karthik Nayak <karthik.188@gmail.com> writes: > >> The corresponding check for reflogs in `reftable_writer_add_log` is >> however missing. Add a similar check, but only check for the upper >> limit. This is because reflogs are treated a bit differently than refs. >> Each reflog entry in reftable has an associated update_index and we also >> allow expiring entries in the middle, which is done by simply writing a >> new reflog entry with the same update_index. This means, writing reflog >> entries with update_index lesser than the writer's update_index is an >> expected scenario. >> >> Add a new unit test to check for the limits and fix some of the existing >> tests, which were setting arbitrary values for the update_index by >> ensuring they stay within the now checked limits. > > Interesting. I am a little curious how this was found. As long as > the other parts of the system (i.e. the callers) are not buggy, the > update pointer will stay within the range, and that is why I do not > think I can write an end-to-end test to trigger an existing bug that > would be caught by this "fix". I was reading the reftable code and noticed it. So mostly luck. Agreed with what you're saying, I'd say this is mostly a 'safeguard'. > Fixing the existing unit tests that > feed a wrong input and expect some right outcome is good, but would > it be a good to also have a new unit test that validates that such > an incorrect precondition for calling is caught and the caller gets > the expected REFTABLE_API_ERROR as a result, I wonder? I'm confused, I did add a _new_ test in this patch to do exactly what you're suggesting. > Being able > to trigger a condition that is harder to do with the end-to-end test > is one good thing about having unit test framework, no? > Totally, these kind of specific changes are perfect for unit-tests. Plus it was very simple to add one too. > Will queue. Thanks. Thanks! Karthik
karthik nayak <karthik.188@gmail.com> writes: > I was reading the reftable code and noticed it. So mostly luck. Agreed > with what you're saying, I'd say this is mostly a 'safeguard'. I was wondering as well, thanks for that info. > Totally, these kind of specific changes are perfect for unit-tests. Plus > it was very simple to add one too. I appreciate you're doing this, that makes it easier to reason about these changes. >> Will queue. Thanks. Junio, thank you for the quick turnarond on this patch. For what it's worth, I've given this a review and I approve these changes.
diff --git a/reftable/writer.c b/reftable/writer.c index fd136794d5a27b33b5017f36fbd6b095ab8dac5b..f87086777cd20a9890a63f10c5d6932310dd5610 100644 --- a/reftable/writer.c +++ b/reftable/writer.c @@ -412,6 +412,18 @@ int reftable_writer_add_log(struct reftable_writer *w, if (log->value_type == REFTABLE_LOG_DELETION) return reftable_writer_add_log_verbatim(w, log); + /* + * Verify only the upper limit of the update_index. Each reflog entry + * is tied to a specific update_index. Entries in the reflog can be + * replaced by adding a new entry with the same update_index, + * effectively canceling the old one. + * + * Consequently, reflog updates may include update_index values lower + * than the writer's min_update_index. + */ + if (log->update_index > w->max_update_index) + return REFTABLE_API_ERROR; + if (!log->refname) return REFTABLE_API_ERROR; diff --git a/t/unit-tests/t-reftable-readwrite.c b/t/unit-tests/t-reftable-readwrite.c index d279b86df0aeda11b3fb4d2c15803760ae394941..7ffbd78c6759fc85ab9d5a2909d4d20662fc56c7 100644 --- a/t/unit-tests/t-reftable-readwrite.c +++ b/t/unit-tests/t-reftable-readwrite.c @@ -90,7 +90,7 @@ static void t_log_buffer_size(void) int i; struct reftable_log_record log = { .refname = (char *) "refs/heads/master", - .update_index = 0xa, + .update_index = update_index, .value_type = REFTABLE_LOG_UPDATE, .value = { .update = { .name = (char *) "Han-Wen Nienhuys", @@ -127,7 +127,7 @@ static void t_log_overflow(void) int err; struct reftable_log_record log = { .refname = (char *) "refs/heads/master", - .update_index = 0xa, + .update_index = update_index, .value_type = REFTABLE_LOG_UPDATE, .value = { .update = { @@ -151,6 +151,48 @@ static void t_log_overflow(void) reftable_buf_release(&buf); } +static void t_log_write_limits(void) +{ + struct reftable_write_options opts = { 0 }; + struct reftable_buf buf = REFTABLE_BUF_INIT; + struct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts); + struct reftable_log_record log = { + .refname = (char *)"refs/head/master", + .update_index = 0, + .value_type = REFTABLE_LOG_UPDATE, + .value = { + .update = { + .old_hash = { 1 }, + .new_hash = { 2 }, + .name = (char *)"Han-Wen Nienhuys", + .email = (char *)"hanwen@google.com", + .tz_offset = 100, + .time = 0x5e430672, + }, + }, + }; + int err; + + reftable_writer_set_limits(w, 1, 1); + + /* write with update_index (0) below set limits (1, 1) */ + err = reftable_writer_add_log(w, &log); + check_int(err, ==, 0); + + /* write with update_index (1) in the set limits (1, 1) */ + log.update_index = 1; + err = reftable_writer_add_log(w, &log); + check_int(err, ==, 0); + + /* write with update_index (3) above set limits (1, 1) */ + log.update_index = 3; + err = reftable_writer_add_log(w, &log); + check_int(err, ==, REFTABLE_API_ERROR); + + reftable_writer_free(w); + reftable_buf_release(&buf); +} + static void t_log_write_read(void) { struct reftable_write_options opts = { @@ -917,6 +959,7 @@ int cmd_main(int argc UNUSED, const char *argv[] UNUSED) TEST(t_corrupt_table_empty(), "read-write on an empty table"); TEST(t_log_buffer_size(), "buffer extension for log compression"); TEST(t_log_overflow(), "log overflow returns expected error"); + TEST(t_log_write_limits(), "writer limits for writing log records"); TEST(t_log_write_read(), "read-write on log records"); TEST(t_log_zlib_corruption(), "reading corrupted log record returns expected error"); TEST(t_table_read_api(), "read on a table"); diff --git a/t/unit-tests/t-reftable-stack.c b/t/unit-tests/t-reftable-stack.c index 72f6747064f62106e87c9a90e5fe315139d46e60..52b81475c36aa94ff09f3bf77a7d23992957deb4 100644 --- a/t/unit-tests/t-reftable-stack.c +++ b/t/unit-tests/t-reftable-stack.c @@ -770,8 +770,12 @@ static void t_reftable_stack_tombstone(void) } logs[i].refname = xstrdup(buf); - /* update_index is part of the key. */ - logs[i].update_index = 42; + /* + * update_index is part of the key so should be constant. + * The value itself should be less than the writer's upper + * limit. + */ + logs[i].update_index = 1; if (i % 2 == 0) { logs[i].value_type = REFTABLE_LOG_UPDATE; t_reftable_set_hash(logs[i].value.update.new_hash, i,
Each reftable addition has an associated update_index. While writing refs, the update_index is verified to be within the range of the reftable writer, i.e. `writer.min_update_index <= ref.update_index` and `writer.max_update_index => ref.update_index`. The corresponding check for reflogs in `reftable_writer_add_log` is however missing. Add a similar check, but only check for the upper limit. This is because reflogs are treated a bit differently than refs. Each reflog entry in reftable has an associated update_index and we also allow expiring entries in the middle, which is done by simply writing a new reflog entry with the same update_index. This means, writing reflog entries with update_index lesser than the writer's update_index is an expected scenario. Add a new unit test to check for the limits and fix some of the existing tests, which were setting arbitrary values for the update_index by ensuring they stay within the now checked limits. Signed-off-by: Karthik Nayak <karthik.188@gmail.com> --- Changes in v2: - Update the commit message to have an inclusive rannge. - Update the unit test to also include an update with index below range. - Link to v1: https://lore.kernel.org/r/20241205-424-reftable-writer-add-check-for-limits-v1-1-b287b055204e@gmail.com --- reftable/writer.c | 12 ++++++++++ t/unit-tests/t-reftable-readwrite.c | 47 +++++++++++++++++++++++++++++++++++-- t/unit-tests/t-reftable-stack.c | 8 +++++-- 3 files changed, 63 insertions(+), 4 deletions(-) --- Range-diff versus v1: 1: e30bdaeeee ! 1: 1c3cb5e92b reftable/writer: ensure valid range for log's update_index @@ Commit message Each reftable addition has an associated update_index. While writing refs, the update_index is verified to be within the range of the - reftable writer, i.e. `writer.min_update_index < ref.update_index` and - `writer.max_update_index > ref.update_index`. + reftable writer, i.e. `writer.min_update_index <= ref.update_index` and + `writer.max_update_index => ref.update_index`. The corresponding check for reflogs in `reftable_writer_add_log` is however missing. Add a similar check, but only check for the upper @@ t/unit-tests/t-reftable-readwrite.c: static void t_log_overflow(void) + struct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts); + struct reftable_log_record log = { + .refname = (char *)"refs/head/master", -+ .update_index = 1, ++ .update_index = 0, + .value_type = REFTABLE_LOG_UPDATE, + .value = { + .update = { @@ t/unit-tests/t-reftable-readwrite.c: static void t_log_overflow(void) + }; + int err; + -+ reftable_writer_set_limits(w, 1, 2); ++ reftable_writer_set_limits(w, 1, 1); + ++ /* write with update_index (0) below set limits (1, 1) */ + err = reftable_writer_add_log(w, &log); + check_int(err, ==, 0); + -+ log.update_index = 2; ++ /* write with update_index (1) in the set limits (1, 1) */ ++ log.update_index = 1; + err = reftable_writer_add_log(w, &log); + check_int(err, ==, 0); + ++ /* write with update_index (3) above set limits (1, 1) */ + log.update_index = 3; + err = reftable_writer_add_log(w, &log); + check_int(err, ==, REFTABLE_API_ERROR); --- base-commit: cc01bad4a9f566cf4453c7edd6b433851b0835e2 change-id: 20241120-424-reftable-writer-add-check-for-limits-01b1fb703528 Thanks - Karthik