Skip to content

Commit 85da2a2

Browse files
pks-tgitster
authored andcommitted
reftable/stack: fix segfault when reload with reused readers fails
It is expected that reloading the stack fails with concurrent writers, e.g. because a table that we just wanted to read just got compacted. In case we decided to reuse readers this will cause a segfault though because we unconditionally release all new readers, including the reused ones. As those are still referenced by the current stack, the result is that we will eventually try to dereference those already-freed readers. Fix this bug by incrementing the refcount of reused readers temporarily. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 1302ed6 commit 85da2a2

File tree

2 files changed

+82
-0
lines changed

2 files changed

+82
-0
lines changed

reftable/stack.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,8 @@ static int reftable_stack_reload_once(struct reftable_stack *st,
226226
{
227227
size_t cur_len = !st->merged ? 0 : st->merged->readers_len;
228228
struct reftable_reader **cur = stack_copy_readers(st, cur_len);
229+
struct reftable_reader **reused = NULL;
230+
size_t reused_len = 0, reused_alloc = 0;
229231
size_t names_len = names_length(names);
230232
struct reftable_reader **new_readers =
231233
reftable_calloc(names_len, sizeof(*new_readers));
@@ -245,6 +247,18 @@ static int reftable_stack_reload_once(struct reftable_stack *st,
245247
if (cur[i] && 0 == strcmp(cur[i]->name, name)) {
246248
rd = cur[i];
247249
cur[i] = NULL;
250+
251+
/*
252+
* When reloading the stack fails, we end up
253+
* releasing all new readers. This also
254+
* includes the reused readers, even though
255+
* they are still in used by the old stack. We
256+
* thus need to keep them alive here, which we
257+
* do by bumping their refcount.
258+
*/
259+
REFTABLE_ALLOC_GROW(reused, reused_len + 1, reused_alloc);
260+
reused[reused_len++] = rd;
261+
reftable_reader_incref(rd);
248262
break;
249263
}
250264
}
@@ -301,10 +315,19 @@ static int reftable_stack_reload_once(struct reftable_stack *st,
301315
new_readers = NULL;
302316
new_readers_len = 0;
303317

318+
/*
319+
* Decrement the refcount of reused readers again. This only needs to
320+
* happen on the successful case, because on the unsuccessful one we
321+
* decrement their refcount via `new_readers`.
322+
*/
323+
for (i = 0; i < reused_len; i++)
324+
reftable_reader_decref(reused[i]);
325+
304326
done:
305327
for (i = 0; i < new_readers_len; i++)
306328
reftable_reader_decref(new_readers[i]);
307329
reftable_free(new_readers);
330+
reftable_free(reused);
308331
reftable_free(cur);
309332
strbuf_release(&table_path);
310333
return err;

reftable/stack_test.c

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ license that can be found in the LICENSE file or at
1010

1111
#include "system.h"
1212

13+
#include "copy.h"
1314
#include "reftable-reader.h"
1415
#include "merged.h"
1516
#include "basics.h"
@@ -1125,6 +1126,63 @@ static void test_reftable_stack_read_across_reload(void)
11251126
clear_dir(dir);
11261127
}
11271128

1129+
static void test_reftable_stack_reload_with_missing_table(void)
1130+
{
1131+
struct reftable_write_options opts = { 0 };
1132+
struct reftable_stack *st = NULL;
1133+
struct reftable_ref_record rec = { 0 };
1134+
struct reftable_iterator it = { 0 };
1135+
struct strbuf table_path = STRBUF_INIT, content = STRBUF_INIT;
1136+
char *dir = get_tmp_dir(__LINE__);
1137+
int err;
1138+
1139+
/* Create a first stack and set up an iterator for it. */
1140+
err = reftable_new_stack(&st, dir, &opts);
1141+
EXPECT_ERR(err);
1142+
write_n_ref_tables(st, 2);
1143+
EXPECT(st->merged->readers_len == 2);
1144+
reftable_stack_init_ref_iterator(st, &it);
1145+
err = reftable_iterator_seek_ref(&it, "");
1146+
EXPECT_ERR(err);
1147+
1148+
/*
1149+
* Update the tables.list file with some garbage data, while reusing
1150+
* our old readers. This should trigger a partial reload of the stack,
1151+
* where we try to reuse our old readers.
1152+
*/
1153+
strbuf_addf(&content, "%s\n", st->readers[0]->name);
1154+
strbuf_addf(&content, "%s\n", st->readers[1]->name);
1155+
strbuf_addstr(&content, "garbage\n");
1156+
strbuf_addf(&table_path, "%s.lock", st->list_file);
1157+
write_file_buf(table_path.buf, content.buf, content.len);
1158+
err = rename(table_path.buf, st->list_file);
1159+
EXPECT_ERR(err);
1160+
1161+
err = reftable_stack_reload(st);
1162+
EXPECT(err == -4);
1163+
EXPECT(st->merged->readers_len == 2);
1164+
1165+
/*
1166+
* Even though the reload has failed, we should be able to continue
1167+
* using the iterator.
1168+
*/
1169+
err = reftable_iterator_next_ref(&it, &rec);
1170+
EXPECT_ERR(err);
1171+
EXPECT(!strcmp(rec.refname, "refs/heads/branch-0000"));
1172+
err = reftable_iterator_next_ref(&it, &rec);
1173+
EXPECT_ERR(err);
1174+
EXPECT(!strcmp(rec.refname, "refs/heads/branch-0001"));
1175+
err = reftable_iterator_next_ref(&it, &rec);
1176+
EXPECT(err > 0);
1177+
1178+
reftable_ref_record_release(&rec);
1179+
reftable_iterator_destroy(&it);
1180+
reftable_stack_destroy(st);
1181+
strbuf_release(&table_path);
1182+
strbuf_release(&content);
1183+
clear_dir(dir);
1184+
}
1185+
11281186
int stack_test_main(int argc, const char *argv[])
11291187
{
11301188
RUN_TEST(test_empty_add);
@@ -1148,6 +1206,7 @@ int stack_test_main(int argc, const char *argv[])
11481206
RUN_TEST(test_reftable_stack_update_index_check);
11491207
RUN_TEST(test_reftable_stack_uptodate);
11501208
RUN_TEST(test_reftable_stack_read_across_reload);
1209+
RUN_TEST(test_reftable_stack_reload_with_missing_table);
11511210
RUN_TEST(test_suggest_compaction_segment);
11521211
RUN_TEST(test_suggest_compaction_segment_nothing);
11531212
return 0;

0 commit comments

Comments
 (0)