Skip to content

Commit 2a7d6de

Browse files
authored
Merge pull request libgit2#5276 from pks-t/pks/patch-fuzzing-fixes
patch_parse: fixes for fuzzing errors
2 parents a31f4c4 + 37141ff commit 2a7d6de

File tree

4 files changed

+116
-7
lines changed

4 files changed

+116
-7
lines changed

src/integer.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,25 @@ GIT_INLINE(int) git__is_int(long long p)
7272
# error compiler has add with overflow intrinsics but SIZE_MAX is unknown
7373
# endif
7474

75+
# define git__add_int_overflow(out, one, two) \
76+
__builtin_sadd_overflow(one, two, out)
77+
# define git__sub_int_overflow(out, one, two) \
78+
__builtin_ssub_overflow(one, two, out)
79+
7580
/* Use Microsoft's safe integer handling functions where available */
7681
#elif defined(_MSC_VER)
7782

83+
# define ENABLE_INTSAFE_SIGNED_FUNCTIONS
7884
# include <intsafe.h>
7985

8086
# define git__add_sizet_overflow(out, one, two) \
8187
(SizeTAdd(one, two, out) != S_OK)
8288
# define git__multiply_sizet_overflow(out, one, two) \
8389
(SizeTMult(one, two, out) != S_OK)
90+
#define git__add_int_overflow(out, one, two) \
91+
(IntAdd(one, two, out) != S_OK)
92+
#define git__sub_int_overflow(out, one, two) \
93+
(IntSub(one, two, out) != S_OK)
8494

8595
#else
8696

@@ -108,6 +118,24 @@ GIT_INLINE(bool) git__multiply_sizet_overflow(size_t *out, size_t one, size_t tw
108118
return false;
109119
}
110120

121+
GIT_INLINE(bool) git__add_int_overflow(int *out, int one, int two)
122+
{
123+
if ((two > 0 && one > (INT_MAX - two)) ||
124+
(two < 0 && one < (INT_MIN - two)))
125+
return true;
126+
*out = one + two;
127+
return false;
128+
}
129+
130+
GIT_INLINE(bool) git__sub_int_overflow(int *out, int one, int two)
131+
{
132+
if ((two > 0 && one < (INT_MIN + two)) ||
133+
(two < 0 && one > (INT_MAX + two)))
134+
return true;
135+
*out = one - two;
136+
return false;
137+
}
138+
111139
#endif
112140

113141
#endif

src/patch_parse.c

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ static int parse_header_path_buf(git_buf *path, git_patch_parse_ctx *ctx, size_t
6969
{
7070
int error;
7171

72+
if (!path_len)
73+
return git_parse_err("patch contains empty path at line %"PRIuZ,
74+
ctx->parse_ctx.line_num);
75+
7276
if ((error = git_buf_put(path, ctx->parse_ctx.line, path_len)) < 0)
7377
goto done;
7478

@@ -91,10 +95,14 @@ static int parse_header_path_buf(git_buf *path, git_patch_parse_ctx *ctx, size_t
9195
static int parse_header_path(char **out, git_patch_parse_ctx *ctx)
9296
{
9397
git_buf path = GIT_BUF_INIT;
94-
int error = parse_header_path_buf(&path, ctx, header_path_len(ctx));
98+
int error;
9599

100+
if ((error = parse_header_path_buf(&path, ctx, header_path_len(ctx))) < 0)
101+
goto out;
96102
*out = git_buf_detach(&path);
97103

104+
out:
105+
git_buf_dispose(&path);
98106
return error;
99107
}
100108

@@ -104,6 +112,12 @@ static int parse_header_git_oldpath(
104112
git_buf old_path = GIT_BUF_INIT;
105113
int error;
106114

115+
if (patch->old_path) {
116+
error = git_parse_err("patch contains duplicate old path at line %"PRIuZ,
117+
ctx->parse_ctx.line_num);
118+
goto out;
119+
}
120+
107121
if ((error = parse_header_path_buf(&old_path, ctx, ctx->parse_ctx.line_len - 1)) < 0)
108122
goto out;
109123

@@ -120,9 +134,14 @@ static int parse_header_git_newpath(
120134
git_buf new_path = GIT_BUF_INIT;
121135
int error;
122136

123-
if ((error = parse_header_path_buf(&new_path, ctx, ctx->parse_ctx.line_len - 1)) < 0)
137+
if (patch->new_path) {
138+
error = git_parse_err("patch contains duplicate new path at line %"PRIuZ,
139+
ctx->parse_ctx.line_num);
124140
goto out;
141+
}
125142

143+
if ((error = parse_header_path_buf(&new_path, ctx, ctx->parse_ctx.line_len - 1)) < 0)
144+
goto out;
126145
patch->new_path = git_buf_detach(&new_path);
127146

128147
out:
@@ -564,11 +583,17 @@ static int parse_hunk_body(
564583
!git_parse_ctx_contains_s(&ctx->parse_ctx, "@@ -");
565584
git_parse_advance_line(&ctx->parse_ctx)) {
566585

586+
int old_lineno, new_lineno, origin, prefix = 1;
567587
char c;
568-
int origin;
569-
int prefix = 1;
570-
int old_lineno = hunk->hunk.old_start + (hunk->hunk.old_lines - oldlines);
571-
int new_lineno = hunk->hunk.new_start + (hunk->hunk.new_lines - newlines);
588+
589+
if (git__add_int_overflow(&old_lineno, hunk->hunk.old_start, hunk->hunk.old_lines) ||
590+
git__sub_int_overflow(&old_lineno, old_lineno, oldlines) ||
591+
git__add_int_overflow(&new_lineno, hunk->hunk.new_start, hunk->hunk.new_lines) ||
592+
git__sub_int_overflow(&new_lineno, new_lineno, newlines)) {
593+
error = git_parse_err("unrepresentable line count at line %"PRIuZ,
594+
ctx->parse_ctx.line_num);
595+
goto done;
596+
}
572597

573598
if (ctx->parse_ctx.line_len == 0 || ctx->parse_ctx.line[ctx->parse_ctx.line_len - 1] != '\n') {
574599
error = git_parse_err("invalid patch instruction at line %"PRIuZ,
@@ -628,6 +653,7 @@ static int parse_hunk_body(
628653

629654
line->content_len = ctx->parse_ctx.line_len - prefix;
630655
line->content = git__strndup(ctx->parse_ctx.line + prefix, line->content_len);
656+
GIT_ERROR_CHECK_ALLOC(line->content);
631657
line->content_offset = ctx->parse_ctx.content_len - ctx->parse_ctx.remain_len;
632658
line->origin = origin;
633659
line->num_lines = 1;
@@ -667,8 +693,9 @@ static int parse_hunk_body(
667693

668694
memset(line, 0x0, sizeof(git_diff_line));
669695

670-
line->content = git__strdup(ctx->parse_ctx.line);
671696
line->content_len = ctx->parse_ctx.line_len;
697+
line->content = git__strndup(ctx->parse_ctx.line, line->content_len);
698+
GIT_ERROR_CHECK_ALLOC(line->content);
672699
line->content_offset = ctx->parse_ctx.content_len - ctx->parse_ctx.remain_len;
673700
line->origin = eof_for_origin(last_origin);
674701
line->num_lines = 1;

tests/patch/parse.c

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,3 +148,36 @@ void test_patch_parse__lifetime_of_patch_does_not_depend_on_buffer(void)
148148

149149
git_patch_free(patch);
150150
}
151+
152+
void test_patch_parse__binary_file_with_missing_paths(void)
153+
{
154+
git_patch *patch;
155+
cl_git_fail(git_patch_from_buffer(&patch, PATCH_BINARY_FILE_WITH_MISSING_PATHS,
156+
strlen(PATCH_BINARY_FILE_WITH_MISSING_PATHS), NULL));
157+
}
158+
159+
void test_patch_parse__memory_leak_on_multiple_paths(void)
160+
{
161+
git_patch *patch;
162+
cl_git_fail(git_patch_from_buffer(&patch, PATCH_MULTIPLE_OLD_PATHS, strlen(PATCH_MULTIPLE_OLD_PATHS), NULL));
163+
}
164+
165+
void test_patch_parse__truncated_no_newline_at_end_of_file(void)
166+
{
167+
size_t len = strlen(PATCH_APPEND_NO_NL) - strlen("at end of file\n");
168+
const git_diff_line *line;
169+
git_patch *patch;
170+
171+
cl_git_pass(git_patch_from_buffer(&patch, PATCH_APPEND_NO_NL, len, NULL));
172+
cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 4));
173+
cl_assert_equal_s(line->content, "\\ No newline ");
174+
175+
git_patch_free(patch);
176+
}
177+
178+
void test_patch_parse__line_number_overflow(void)
179+
{
180+
git_patch *patch;
181+
cl_git_fail(git_patch_from_buffer(&patch, PATCH_INTMAX_NEW_LINES, strlen(PATCH_INTMAX_NEW_LINES), NULL));
182+
git_patch_free(patch);
183+
}

tests/patch/patch_common.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -905,3 +905,24 @@
905905
"-b\n" \
906906
"+bb\n" \
907907
" c\n"
908+
909+
#define PATCH_BINARY_FILE_WITH_MISSING_PATHS \
910+
"diff --git \n" \
911+
"--- \n" \
912+
"+++ \n" \
913+
"Binary files "
914+
915+
#define PATCH_MULTIPLE_OLD_PATHS \
916+
"diff --git \n" \
917+
"--- \n" \
918+
"+++ \n" \
919+
"index 0000..7DDb\n" \
920+
"--- \n"
921+
922+
#define PATCH_INTMAX_NEW_LINES \
923+
"diff --git a/file b/file\n" \
924+
"--- a/file\n" \
925+
"+++ b/file\n" \
926+
"@@ -0 +2147483647 @@\n" \
927+
"\n" \
928+
" "

0 commit comments

Comments
 (0)