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>
Patrick Steinhardt committed
Aug 23, 2024 at 16:12 UTC
85da2a2ab62e24a8b9ff183fe3a451b445632487
2 files changed
+82
reftable/stack.c
+23
@@ -226,6 +226,8 @@ static int reftable_stack_reload_once(struct reftable_stack *st,
226
{
227
size_t cur_len = !st->merged ? 0 : st->merged->readers_len;
228
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;
231
size_t names_len = names_length(names);
232
struct reftable_reader **new_readers =
233
reftable_calloc(names_len, sizeof(*new_readers));
@@ -245,6 +247,18 @@ static int reftable_stack_reload_once(struct reftable_stack *st,
247
if (cur[i] && 0 == strcmp(cur[i]->name, name)) {
248
rd = cur[i];
249
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);
262
break;
263
}
264
}
@@ -301,10 +315,19 @@ static int reftable_stack_reload_once(struct reftable_stack *st,
315
new_readers = NULL;
316
new_readers_len = 0;
317
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
+
326
done:
327
for (i = 0; i < new_readers_len; i++)
328
reftable_reader_decref(new_readers[i]);
329
reftable_free(new_readers);
330
+ reftable_free(reused);
331
reftable_free(cur);
332
strbuf_release(&table_path);
333
return err;
reftable/stack_test.c
+59
@@ -10,6 +10,7 @@ https://developers.google.com/open-source/licenses/bsd
10
11
#include "system.h"
12
13
+#include "copy.h"
14
#include "reftable-reader.h"
15
#include "merged.h"
16
#include "basics.h"
@@ -1125,6 +1126,63 @@ static void test_reftable_stack_read_across_reload(void)
1126
clear_dir(dir);
1127
}
1128
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
+
1186
int stack_test_main(int argc, const char *argv[])
1187
{
1188
RUN_TEST(test_empty_add);
@@ -1148,6 +1206,7 @@ int stack_test_main(int argc, const char *argv[])
1206
RUN_TEST(test_reftable_stack_update_index_check);
1207
RUN_TEST(test_reftable_stack_uptodate);
1208
RUN_TEST(test_reftable_stack_read_across_reload);
1209
+ RUN_TEST(test_reftable_stack_reload_with_missing_table);
1210
RUN_TEST(test_suggest_compaction_segment);
1211
RUN_TEST(test_suggest_compaction_segment_nothing);
1212
return 0;