Skip to content

Commit 7e2619d

Browse files
peffgitster
authored andcommitted
list_objects_filter_options: plug leak of filter_spec strings
The list_objects_filter_options struct contains a string_list to store the filter_spec. Because we allow the options struct to be zero-initialized by callers, the string_list's strdup_strings field is generally not set. Because we don't want to depend on the memory lifetimes of any strings passed in to the list-objects API, everything we add to the string_list is duplicated (either via xstrdup(), or via strbuf_detach()). So far so good, but now we have a problem at cleanup time: when we clear the list, the string_list API doesn't realize that it needs to free all of those strings, and we leak them. One option would be to set strdup_strings right before clearing the list. But this is tricky for two reasons: 1. There's one spot, in partial_clone_get_default_filter_spec(), that fails to duplicate its argument. We could fix that, but... 2. We clear the list in a surprising number of places. As you might expect, we do so in list_objects_filter_release(). But we also clear and rewrite it in expand_list_objects_filter_spec(), list_objects_filter_spec(), and transform_to_combine_type(). We'd have to put the same hack in all of those spots. Instead, let's just set strdup_strings before adding anything. That lets us drop the extra manual xstrdup() calls, fixes the spot mentioned in (1) above that _should_ be duplicating, and future-proofs further calls. We do have to switch the strbuf_detach() calls to use the nodup form, but that's an easy change, and the resulting code more clearly shows the expected ownership transfer. This also resolves a weird inconsistency: when we make a deep copy with list_objects_filter_copy(), it initializes the copy's filter_spec with string_list_init_dup(). So the copy frees its string_list memory correctly, but accidentally leaks the extra manual-xstrdup()'d strings! There is one hiccup, though. In an ideal world, everyone would allocate the list_objects_filter_options struct with an initializer which used STRING_LIST_INIT_DUP under the hood. But there are a bunch of existing callers which think that zero-initializing is good enough. We can leave them as-is by noting that the list is always initially populated via parse_list_objects_filter(). So we can just initialize the strdup_strings flag there. This is arguably a band-aid, but it works reliably. And it doesn't make anything harder if we want to switch all the callers later to a new LIST_OBJECTS_FILTER_INIT. Signed-off-by: Jeff King <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent dd49699 commit 7e2619d

File tree

1 file changed

+13
-6
lines changed

1 file changed

+13
-6
lines changed

list-objects-filter-options.c

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ static void filter_spec_append_urlencode(
207207
struct strbuf buf = STRBUF_INIT;
208208
strbuf_addstr_urlencode(&buf, raw, allow_unencoded);
209209
trace_printf("Add to combine filter-spec: %s\n", buf.buf);
210-
string_list_append(&filter->filter_spec, strbuf_detach(&buf, NULL));
210+
string_list_append_nodup(&filter->filter_spec, strbuf_detach(&buf, NULL));
211211
}
212212

213213
/*
@@ -226,12 +226,13 @@ static void transform_to_combine_type(
226226
xcalloc(initial_sub_alloc, sizeof(*sub_array));
227227
sub_array[0] = *filter_options;
228228
memset(filter_options, 0, sizeof(*filter_options));
229+
string_list_init_dup(&filter_options->filter_spec);
229230
filter_options->sub = sub_array;
230231
filter_options->sub_alloc = initial_sub_alloc;
231232
}
232233
filter_options->sub_nr = 1;
233234
filter_options->choice = LOFC_COMBINE;
234-
string_list_append(&filter_options->filter_spec, xstrdup("combine:"));
235+
string_list_append(&filter_options->filter_spec, "combine:");
235236
filter_spec_append_urlencode(
236237
filter_options,
237238
list_objects_filter_spec(&filter_options->sub[0]));
@@ -256,8 +257,14 @@ void parse_list_objects_filter(
256257
struct strbuf errbuf = STRBUF_INIT;
257258
int parse_error;
258259

260+
if (!filter_options->filter_spec.strdup_strings) {
261+
if (filter_options->filter_spec.nr)
262+
BUG("unexpected non-allocated string in filter_spec");
263+
filter_options->filter_spec.strdup_strings = 1;
264+
}
265+
259266
if (!filter_options->choice) {
260-
string_list_append(&filter_options->filter_spec, xstrdup(arg));
267+
string_list_append(&filter_options->filter_spec, arg);
261268

262269
parse_error = gently_parse_list_objects_filter(
263270
filter_options, arg, &errbuf);
@@ -268,7 +275,7 @@ void parse_list_objects_filter(
268275
*/
269276
transform_to_combine_type(filter_options);
270277

271-
string_list_append(&filter_options->filter_spec, xstrdup("+"));
278+
string_list_append(&filter_options->filter_spec, "+");
272279
filter_spec_append_urlencode(filter_options, arg);
273280
ALLOC_GROW_BY(filter_options->sub, filter_options->sub_nr, 1,
274281
filter_options->sub_alloc);
@@ -306,7 +313,7 @@ const char *list_objects_filter_spec(struct list_objects_filter_options *filter)
306313
strbuf_add_separated_string_list(
307314
&concatted, "", &filter->filter_spec);
308315
string_list_clear(&filter->filter_spec, /*free_util=*/0);
309-
string_list_append(
316+
string_list_append_nodup(
310317
&filter->filter_spec, strbuf_detach(&concatted, NULL));
311318
}
312319

@@ -321,7 +328,7 @@ const char *expand_list_objects_filter_spec(
321328
strbuf_addf(&expanded_spec, "blob:limit=%lu",
322329
filter->blob_limit_value);
323330
string_list_clear(&filter->filter_spec, /*free_util=*/0);
324-
string_list_append(
331+
string_list_append_nodup(
325332
&filter->filter_spec,
326333
strbuf_detach(&expanded_spec, NULL));
327334
}

0 commit comments

Comments
 (0)