Skip to content

Commit cf07db2

Browse files
committed
filter: only close filter if it's been initialized correctly
In the function `git_filter_list_stream_data`, we initialize, write and subesquently close the stream which should receive content processed by the filter. While we skip writing to the stream if its initialization failed, we still try to close it unconditionally -- even if the initialization failed, where the stream might not be set at all, leading us to segfault. Semantics in this code is not really clear. The function handling the same logic for files instead of data seems to do the right thing here in only closing the stream when initialization succeeded. When stepping back a bit, this is only reasonable: if a stream cannot be initialized, the caller would not expect it to be closed again. So actually, both callers of `stream_list_init` fail to do so. The data streaming function will always close the stream and the file streaming function will not close the stream if writing to it has failed. The fix is thus two-fold: - callers of `stream_list_init` now close the stream iff it has been initialized - `stream_list_init` now closes the lastly initialized stream if the current stream in the chain failed to initialize Add a test which segfaulted previous to these changes.
1 parent e572b63 commit cf07db2

File tree

4 files changed

+78
-13
lines changed

4 files changed

+78
-13
lines changed

src/filter.c

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -911,14 +911,19 @@ static int stream_list_init(
911911
last_stream);
912912

913913
if (error < 0)
914-
return error;
914+
goto out;
915915

916916
git_vector_insert(streams, filter_stream);
917917
last_stream = filter_stream;
918918
}
919919

920-
*out = last_stream;
921-
return 0;
920+
out:
921+
if (error)
922+
last_stream->close(last_stream);
923+
else
924+
*out = last_stream;
925+
926+
return error;
922927
}
923928

924929
void stream_list_free(git_vector *streams)
@@ -943,12 +948,13 @@ int git_filter_list_stream_file(
943948
git_vector filter_streams = GIT_VECTOR_INIT;
944949
git_writestream *stream_start;
945950
ssize_t readlen;
946-
int fd = -1, error;
951+
int fd = -1, error, initialized = 0;
947952

948953
if ((error = stream_list_init(
949954
&stream_start, &filter_streams, filters, target)) < 0 ||
950955
(error = git_path_join_unrooted(&abspath, path, base, NULL)) < 0)
951956
goto done;
957+
initialized = 1;
952958

953959
if ((fd = git_futils_open_ro(abspath.ptr)) < 0) {
954960
error = fd;
@@ -960,13 +966,13 @@ int git_filter_list_stream_file(
960966
goto done;
961967
}
962968

963-
if (!readlen)
964-
error = stream_start->close(stream_start);
965-
else if (readlen < 0)
969+
if (readlen < 0)
966970
error = readlen;
967971

968-
969972
done:
973+
if (initialized)
974+
error |= stream_start->close(stream_start);
975+
970976
if (fd >= 0)
971977
p_close(fd);
972978
stream_list_free(&filter_streams);
@@ -981,20 +987,24 @@ int git_filter_list_stream_data(
981987
{
982988
git_vector filter_streams = GIT_VECTOR_INIT;
983989
git_writestream *stream_start;
984-
int error = 0, close_error;
990+
int error, initialized = 0;
985991

986992
git_buf_sanitize(data);
987993

988994
if ((error = stream_list_init(&stream_start, &filter_streams, filters, target)) < 0)
989995
goto out;
996+
initialized = 1;
990997

991-
error = stream_start->write(stream_start, data->ptr, data->size);
998+
if ((error = stream_start->write(
999+
stream_start, data->ptr, data->size)) < 0)
1000+
goto out;
9921001

9931002
out:
994-
close_error = stream_start->close(stream_start);
1003+
if (initialized)
1004+
error |= stream_start->close(stream_start);
1005+
9951006
stream_list_free(&filter_streams);
996-
/* propagate the stream init or write error */
997-
return error < 0 ? error : close_error;
1007+
return error;
9981008
}
9991009

10001010
int git_filter_list_stream_blob(

tests/filter/custom.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ void test_filter_custom__initialize(void)
5555
"hero* bitflip reverse\n"
5656
"herofile text\n"
5757
"heroflip -reverse binary\n"
58+
"villain erroneous\n"
5859
"*.bin binary\n");
5960
}
6061

@@ -82,6 +83,11 @@ static void register_custom_filters(void)
8283
create_reverse_filter("+prereverse"),
8384
GIT_FILTER_DRIVER_PRIORITY));
8485

86+
cl_git_pass(git_filter_register(
87+
"erroneous",
88+
create_erroneous_filter("+erroneous"),
89+
GIT_FILTER_DRIVER_PRIORITY));
90+
8591
filters_registered = 1;
8692
}
8793
}
@@ -235,3 +241,18 @@ void test_filter_custom__filter_registry_failure_cases(void)
235241
cl_git_fail(git_filter_unregister(GIT_FILTER_IDENT));
236242
cl_assert_equal_i(GIT_ENOTFOUND, git_filter_unregister("not-a-filter"));
237243
}
244+
245+
void test_filter_custom__erroneous_filter_fails(void)
246+
{
247+
git_filter_list *filters;
248+
git_buf out = GIT_BUF_INIT;
249+
git_buf in = GIT_BUF_INIT_CONST(workdir_data, strlen(workdir_data));
250+
251+
cl_git_pass(git_filter_list_load(
252+
&filters, g_repo, NULL, "villain", GIT_FILTER_TO_WORKTREE, 0));
253+
254+
cl_git_fail(git_filter_list_apply_to_data(&out, filters, &in));
255+
256+
git_filter_list_free(filters);
257+
git_buf_free(&out);
258+
}

tests/filter/custom_helpers.c

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,36 @@ git_filter *create_reverse_filter(const char *attrs)
106106

107107
return filter;
108108
}
109+
110+
int erroneous_filter_stream(
111+
git_writestream **out,
112+
git_filter *self,
113+
void **payload,
114+
const git_filter_source *src,
115+
git_writestream *next)
116+
{
117+
GIT_UNUSED(out);
118+
GIT_UNUSED(self);
119+
GIT_UNUSED(payload);
120+
GIT_UNUSED(src);
121+
GIT_UNUSED(next);
122+
return -1;
123+
}
124+
125+
static void erroneous_filter_free(git_filter *f)
126+
{
127+
git__free(f);
128+
}
129+
130+
git_filter *create_erroneous_filter(const char *attrs)
131+
{
132+
git_filter *filter = git__calloc(1, sizeof(git_filter));
133+
cl_assert(filter);
134+
135+
filter->version = GIT_FILTER_VERSION;
136+
filter->attributes = attrs;
137+
filter->stream = erroneous_filter_stream;
138+
filter->shutdown = erroneous_filter_free;
139+
140+
return filter;
141+
}

tests/filter/custom_helpers.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
extern git_filter *create_bitflip_filter(void);
44
extern git_filter *create_reverse_filter(const char *attr);
5+
extern git_filter *create_erroneous_filter(const char *attr);
56

67
extern int bitflip_filter_apply(
78
git_filter *self,

0 commit comments

Comments
 (0)