Don’t be fooled by NUL bytes in diff directives

* src/pch.c (get_line, pget_line): New arg ALLOW_NUL.
It is true when getting data lines, which can contain NUL,
but false when getting ‘diff’ directives, which cannot.
All uses changed.
* tests/bad-filenames: Check that ‘patch’ rejects
directives containing NUL.
This commit is contained in:
Paul Eggert 2024-09-17 17:37:32 -07:00
parent 79dd5e762c
commit 9ba5eb00b3
3 changed files with 38 additions and 17 deletions

2
NEWS
View File

@ -5,6 +5,8 @@ Unreleased changes:
GNU/Linux platforms where time_t defaults to 32 bits.
* 'patch' no longer creates files with names containing newlines,
as encouraged by POSIX.1-2024.
* Patches can no longer contain NUL ('\0') bytes in diff directive lines.
These bytes would otherwise cause unpredictable behavior.
* Patches can now contain sequences of spaces and tabs around line numbers
and in other places where POSIX requires support for these sequences.
* --enable-gcc-warnings no longer uses expensive static checking.

View File

@ -77,8 +77,8 @@ static bool p_git_diff; /* true if this is a git style diff */
static enum diff intuit_diff_type (bool, mode_t *);
static enum nametype best_name (char * const *, int const *);
static idx_t prefix_components (char *, bool);
static idx_t pget_line (idx_t, idx_t, bool, bool);
static idx_t get_line (void);
static idx_t pget_line (idx_t, idx_t, bool, bool, bool);
static idx_t get_line (bool);
static bool incomplete_line (void);
static void grow_hunkmax (void);
static void malformed (void);
@ -516,7 +516,7 @@ intuit_diff_type (bool need_header, mode_t *p_file_type)
indent = 0;
this_line = Ftello (pfp);
idx_t chars_read = pget_line (0, 0, false, false);
idx_t chars_read = pget_line (0, 0, false, false, false);
if (! chars_read) {
if (first_ed_command_letter) {
/* nothing but deletes!? */
@ -1240,7 +1240,7 @@ another_hunk (enum diff difftype, bool rev)
/* Pacify 'gcc -Wall'. */
fillsrc = filldst = repl_patch_line = repl_context = 0;
idx_t chars_read = get_line ();
idx_t chars_read = get_line (false);
if (chars_read <= 8
|| strncmp (patchbuf, "********", 8) != 0) {
next_intuit_at(line_beginning,p_input_line);
@ -1258,7 +1258,7 @@ another_hunk (enum diff difftype, bool rev)
}
p_hunk_beg = p_input_line + 1;
while (p_end < p_max) {
chars_read = get_line ();
chars_read = get_line (true);
if (!chars_read) {
if (repl_beginning && repl_could_be_missing) {
repl_missing = true;
@ -1606,7 +1606,7 @@ another_hunk (enum diff difftype, bool rev)
idx_t filldst; /* index of new lines */
char ch = '\0';
if (get_line () <= 4
if (get_line (false) <= 4
|| strncmp (patchbuf, "@@ -", 4) != 0) {
next_intuit_at(line_beginning,p_input_line);
return false;
@ -1669,7 +1669,7 @@ another_hunk (enum diff difftype, bool rev)
p_prefix_context = -1;
p_hunk_beg = p_input_line + 1;
while (fillsrc <= p_ptrn_lines || filldst <= p_end) {
idx_t chars_read = get_line ();
idx_t chars_read = get_line (true);
if (!chars_read) {
if (p_max - filldst < 3) {
/* Assume blank lines got chopped. */
@ -1750,7 +1750,7 @@ another_hunk (enum diff difftype, bool rev)
off_t line_beginning = Ftello (pfp);
p_prefix_context = p_suffix_context = 0;
idx_t chars_read = get_line ();
idx_t chars_read = get_line (false);
bool invalid_line = chars_read <= 0;
if (!invalid_line)
for (s = patchbuf; c_isblank (*s); s++)
@ -1798,7 +1798,7 @@ another_hunk (enum diff difftype, bool rev)
idx_t i;
for (i=1; i<=p_ptrn_lines; i++) {
chars_read = get_line ();
chars_read = get_line (true);
if (!chars_read)
fatal ("unexpected end of file in patch at line %td",
p_input_line);
@ -1812,7 +1812,7 @@ another_hunk (enum diff difftype, bool rev)
p_Char[i] = '-';
}
if (hunk_type == 'c') {
chars_read = get_line ();
chars_read = get_line (true);
if (! chars_read)
fatal ("unexpected end of file in patch at line %td",
p_input_line);
@ -1823,7 +1823,7 @@ another_hunk (enum diff difftype, bool rev)
p_line[i] = xmemdup (patchbuf, p_len[i] + 1);
p_Char[i] = '=';
for (i++; i<=p_end; i++) {
chars_read = get_line ();
chars_read = get_line (true);
if (!chars_read)
fatal ("unexpected end of file in patch at line %td",
p_input_line);
@ -1868,10 +1868,10 @@ another_hunk (enum diff difftype, bool rev)
}
static idx_t
get_line (void)
get_line (bool allow_nul)
{
return pget_line (p_indent, p_rfc934_nesting, p_strip_trailing_cr,
p_pass_comments_through);
p_pass_comments_through, allow_nul);
}
/* Input a line from the patch file, worrying about indentation.
@ -1880,18 +1880,21 @@ get_line (void)
If STRIP_TRAILING_CR is true, remove any trailing carriage-return.
Unless PASS_COMMENTS_THROUGH is true, ignore any resulting lines
that begin with '#'; they're comments.
If ALLOW_NUL, allow null bytes in the line; otherwise diagnose and fail.
Ignore any partial lines at end of input, but warn about them.
Succeed if a line was read; it is terminated by "\n\0" for convenience.
Return the number of characters read, including '\n' but not '\0'. */
static idx_t
pget_line (idx_t indent, ptrdiff_t rfc934_nesting, bool strip_trailing_cr,
bool pass_comments_through)
bool pass_comments_through, bool allow_nul)
{
FILE *fp = pfp;
int c;
idx_t i;
char *b;
int invalid_byte = allow_nul ? -1 : 0;
bool got_invalid_byte = false;
do
{
@ -1912,7 +1915,7 @@ pget_line (idx_t indent, ptrdiff_t rfc934_nesting, bool strip_trailing_cr,
else if (c == '\t')
i = (i + 8) & ~7;
else
break;
got_invalid_byte |= c == invalid_byte;
}
i = 0;
@ -1927,6 +1930,7 @@ pget_line (idx_t indent, ptrdiff_t rfc934_nesting, bool strip_trailing_cr,
{
i = 1;
b[0] = '-';
got_invalid_byte |= c == invalid_byte;
break;
}
c = getc (fp);
@ -1947,6 +1951,7 @@ pget_line (idx_t indent, ptrdiff_t rfc934_nesting, bool strip_trailing_cr,
b[i++] = c;
if (c == '\n')
break;
got_invalid_byte |= c == invalid_byte;
c = getc (fp);
if (c < 0)
goto patch_ends_in_middle_of_line;
@ -1956,6 +1961,9 @@ pget_line (idx_t indent, ptrdiff_t rfc934_nesting, bool strip_trailing_cr,
}
while (*b == '#' && !pass_comments_through);
if (got_invalid_byte)
fatal ("patch line %td contains NUL byte", p_input_line);
if (strip_trailing_cr && 2 <= i && b[i - 2] == '\r')
b[i-- - 2] = '\n';
b[i] = '\0';
@ -2321,7 +2329,7 @@ do_ed_script (char *input_name, struct outfile *output, FILE *ofp)
for (;;) {
char ed_command_letter;
beginning_of_this_line = Ftello (pfp);
idx_t chars_read = get_line ();
idx_t chars_read = get_line (false);
if (! chars_read) {
next_intuit_at(beginning_of_this_line,p_input_line);
break;
@ -2332,7 +2340,7 @@ do_ed_script (char *input_name, struct outfile *output, FILE *ofp)
Fwrite (patchbuf, 1, chars_read, tmpfp);
if (ed_command_letter != 'd' && ed_command_letter != 's') {
p_pass_comments_through = true;
while ((chars_read = get_line ()) != 0) {
while ((chars_read = get_line (true)) != 0) {
if (tmpfp)
Fwrite (patchbuf, 1, chars_read, tmpfp);
if (chars_read == 2 && strEQ (patchbuf, ".\n"))

View File

@ -146,6 +146,17 @@ $PATCH: **** output file name contains newline
status: 2
EOF
emit_patch_with_NUL()
{
printf '%s\n' '--- /dev/null'
printf '+++ a\0b\n'
printf '%s\n' '@@ -0,0 +1 @@' '+x'
}
check 'emit_patch_with_NUL | patch; echo status: $?' <<EOF
$PATCH: **** patch line 2 contains NUL byte
status: 2
EOF
mkdir d
cd d
cat > d.diff <<EOF