diff --git a/Makefile.am b/Makefile.am index 2214bf9..46352b0 100644 --- a/Makefile.am +++ b/Makefile.am @@ -432,7 +432,15 @@ TESTS = tests/newline1/run-test \ tests/git-deleted-file/run-test \ tests/git-pure-rename/run-test \ tests/git-diff-edge-cases/run-test \ - tests/malformed-diff-headers/run-test + tests/malformed-diff-headers/run-test \ + tests/fuzzy1/run-test \ + tests/fuzzy2/run-test \ + tests/fuzzy3/run-test \ + tests/fuzzy4/run-test \ + tests/fuzzy5/run-test \ + tests/fuzzy6/run-test \ + tests/fuzzy7/run-test \ + tests/fuzzy8/run-test # Scanner tests (only when scanner-patchfilter is enabled) if USE_SCANNER_PATCHFILTER diff --git a/src/interdiff.c b/src/interdiff.c index d1cc9e2..4f8aa9a 100644 --- a/src/interdiff.c +++ b/src/interdiff.c @@ -37,6 +37,7 @@ #include #include #include +#include #ifdef HAVE_UNISTD_H # include #endif /* HAVE_UNISTD_H */ @@ -47,6 +48,8 @@ #ifdef HAVE_SYS_WAIT_H # include #endif /* HAVE_SYS_WAIT_H */ +#include +#include #include "util.h" #include "diff.h" @@ -110,6 +113,38 @@ struct lines_info { struct lines *tail; }; +struct hunk_info { + char *s; /* Start of hunk */ + size_t len; /* Length of hunk in bytes */ + unsigned long nstart; /* Starting line number */ + unsigned long nend; /* Ending line number (inclusive) */ + int relocated:1, /* Whether or not this hunk was relocated */ + discard:1; /* Whether or not to discard this hunk */ +}; + +struct hunk_reloc { + unsigned long new; /* New starting line number */ + long off; /* Offset from the old starting line number */ + unsigned long fuzz; /* Fuzz amount reported by patch */ + int ignored:1; /* Whether or not this relocation was ignored */ +}; + +struct line_info { + const char *s; /* Start of line */ + size_t len; /* Length of line in bytes */ +}; + +struct xtra_context { + unsigned long num; /* Number of extra context lines */ + char *s; /* String of extra context lines */ + size_t len; /* Length of extra context string in bytes */ +}; + +struct rej_file { + FILE *fp; + unsigned long off; +}; + static int human_readable = 1; static char *diff_opts[100]; static int num_diff_opts = 0; @@ -122,6 +157,8 @@ static int no_revert_omitted = 0; static int use_colors = 0; static int color_option_specified = 0; static int debug = 0; +static int fuzzy = 0; +static int max_fuzz_user = -1; static struct patlist *pat_drop_context = NULL; @@ -934,15 +971,19 @@ output_patch1_only (FILE *p1, FILE *out, int not_reverted) } static int -apply_patch (FILE *patch, const char *file, int reverted) +apply_patch (FILE *patch, const char *file, int reverted, FILE **out) { +#define MAX_PATCH_ARGS 9 + const char *argv[MAX_PATCH_ARGS]; + int argc = 0; const char *basename; unsigned long orig_lines, new_lines; + char *line, *fuzz_arg = NULL; size_t linelen; - char *line; + int fildes[4]; + FILE *r, *w; pid_t child; int status; - FILE *w; basename = strrchr (file, '/'); if (basename) @@ -959,10 +1000,70 @@ apply_patch (FILE *patch, const char *file, int reverted) } } - w = xpipe(PATCH, &child, "w", (char **) (const char *[]) { PATCH, - reverted ? (has_ignore_all_space ? "-Rlsp0" : "-Rsp0") - : (has_ignore_all_space ? "-lsp0" : "-sp0"), - file, NULL }); + /* Add up to MAX_PATCH_ARGS arguments for the patch execution */ + argv[argc++] = PATCH; + argv[argc++] = reverted ? (has_ignore_all_space ? "-Rlp0" : "-Rp0") + : (has_ignore_all_space ? "-lp0" : "-p0"); + if (fuzzy) { + int fuzz = 0; + + /* Don't generate .orig files when we expect rejected hunks */ + argv[argc++] = "--no-backup-if-mismatch"; + + /* When reverting a rejected hunk, use the maximum possible + * fuzz, don't generate .rej files, and don't let patch ask to + * unreverse our hunk. Otherwise, either pass in the user- + * supplied max fuzz, or fuzz all but one pre-context and one + * post-context line by default. */ + if (reverted) { + fuzz = INT_MAX; + argv[argc++] = "--reject-file=-"; + argv[argc++] = "-N"; + } else if (max_fuzz_user >= 0) { + fuzz = max_fuzz_user; + } else if (max_context) { + fuzz = max_context - 1; + } + if (asprintf (&fuzz_arg, "--fuzz=%d", fuzz) < 0) + error (EXIT_FAILURE, errno, "asprintf failed"); + argv[argc++] = fuzz_arg; + } + /* Fuzzy mode needs hunk offset messages. Only silence output when + * piping stdout wasn't requested. */ + if (!out) + argv[argc++] = "--silent"; + argv[argc++] = file; + argv[argc++] = NULL; + + /* Flush any pending writes, set up two pipes, and then fork */ + fflush (NULL); + if (pipe (fildes) == -1 || pipe (&fildes[2]) == -1) + error (EXIT_FAILURE, errno, "pipe failed"); + child = fork (); + if (child == -1) { + perror ("fork"); + exit (1); + } + + if (child == 0) { + /* Keep two pipes: one open to stdin, one to stdout */ + close (0); + close (1); + if (dup (fildes[0]) == -1 || dup (fildes[3]) == -1) + error (EXIT_FAILURE, errno, "dup failed"); + close (fildes[0]); + close (fildes[1]); + close (fildes[2]); + close (fildes[3]); + execvp (argv[0], (char **)argv); + } + free (fuzz_arg); + + /* Open the read and write ends of the two pipes */ + if (!(r = fdopen (fildes[2], "r")) || !(w = fdopen (fildes[1], "w"))) + error (EXIT_FAILURE, errno, "fdopen"); + close (fildes[0]); + close (fildes[3]); fprintf (w, "--- %s\n+++ %s\n", basename, basename); line = NULL; @@ -999,6 +1100,12 @@ apply_patch (FILE *patch, const char *file, int reverted) fclose (w); waitpid (child, &status, 0); + /* Provide the output from patch if requested */ + if (out) + *out = r; + else + fclose (r); + if (line) free (line); @@ -1024,9 +1131,12 @@ trim_context (FILE *f /* positioned at start of @@ line */, unsigned long orig_count, orig_orig_count, new_orig_count; unsigned long new_count, orig_new_count, new_new_count; unsigned long total_count = 0; + char *atat_comment; + ssize_t got; /* Read @@ line. */ - if (getline (&line, &linelen, f) < 0) + got = getline (&line, &linelen, f); + if (got < 0) break; if (line[0] == '\\') { @@ -1036,10 +1146,16 @@ trim_context (FILE *f /* positioned at start of @@ line */, } if (read_atatline (line, &orig_offset, &orig_count, - &new_offset, &new_count)) + &new_offset, &new_count) || + !(atat_comment = strstr (line + 1, "@@"))) error (EXIT_FAILURE, 0, "Line not understood: %s", line); + /* Check if there's a comment after the @@ line to retain */ + if (atat_comment + 3 - line < got) + atat_comment = xstrdup (atat_comment + 2); + else + atat_comment = NULL; orig_orig_count = new_orig_count = orig_count; orig_new_count = new_new_count = new_count; fgetpos (f, &pos); @@ -1100,18 +1216,25 @@ trim_context (FILE *f /* positioned at start of @@ line */, fsetpos (f, &pos); if (new_orig_count != 1 && new_new_count != 1) - print_color (out, LINE_HUNK, "@@ -%lu,%lu +%lu,%lu @@\n", + print_color (out, LINE_HUNK, "@@ -%lu,%lu +%lu,%lu @@", orig_offset, new_orig_count, new_offset, new_new_count); else if (new_orig_count != 1) - print_color (out, LINE_HUNK, "@@ -%lu,%lu +%lu @@\n", + print_color (out, LINE_HUNK, "@@ -%lu,%lu +%lu @@", orig_offset, new_orig_count, new_offset); else if (new_new_count != 1) - print_color (out, LINE_HUNK, "@@ -%lu +%lu,%lu @@\n", + print_color (out, LINE_HUNK, "@@ -%lu +%lu,%lu @@", orig_offset, new_offset, new_new_count); else - print_color (out, LINE_HUNK, "@@ -%lu +%lu @@\n", + print_color (out, LINE_HUNK, "@@ -%lu +%lu @@", orig_offset, new_offset); + if (atat_comment) { + fputs (atat_comment, out); + free (atat_comment); + } else { + fputc ('\n', out); + } + while (total_count--) { enum line_type type; ssize_t got = getline (&line, &linelen, f); @@ -1136,7 +1259,8 @@ trim_context (FILE *f /* positioned at start of @@ line */, fwrite (line, (size_t) got, 1, out); continue; } - print_color (out, type, "%s", line); + print_color (out, type, "%.*s", (int) got - 1, line); + fputc ('\n', out); } } @@ -1149,17 +1273,859 @@ trim_context (FILE *f /* positioned at start of @@ line */, return 0; } +static void +output_rej_hunks (const char *diff, struct rej_file **rej1, + struct rej_file **rej2, FILE *out) +{ + char *line = NULL; + + while (*rej1 || *rej2) { + struct rej_file **rej_ptr = rej1, *rej; + int first_line_done = 0, patch_id = 1; + unsigned long diff_off; + long next_atat_pos; + size_t linelen; + ssize_t got; + + /* Pick the reject hunk that comes first */ + if (!*rej1 || (*rej2 && (*rej2)->off < (*rej1)->off)) { + rej_ptr = rej2; + patch_id = 2; + } + rej = *rej_ptr; + + if (diff) { + /* Wait until the current diff line is an @@ line */ + if (strncmp (diff, "@@ ", 3)) + return; + + if (read_atatline (diff, &diff_off, NULL, NULL, NULL)) + error (EXIT_FAILURE, 0, "line not understood: %s", + diff); + + /* Stop if the diff hunk comes next */ + if (rej->off > diff_off) + return; + } + + /* Write the rej hunk until EOF or the next @@ line (i.e., next + * hunk). Note that rej starts at the current @@ line that we + * must write, so don't look for the next @@ until after the + * first line is written. */ + for (;;) { + got = getline (&line, &linelen, rej->fp); + if (got <= 0) { + if (feof (rej->fp)) + goto rej_file_eof; + error (EXIT_FAILURE, errno, + "Failed to read line from .rej"); + } + if (first_line_done) { + if (!strncmp (line, "@@ ", 3)) + break; + + fwrite (line, (size_t) got, 1, out); + next_atat_pos = ftell (rej->fp); + } else { + /* Append a comment after the @@ line indicating + * this is a rejected hunk. */ + first_line_done = 1; + fwrite (line, (size_t) got - 1, 1, out); + fprintf (out, " INTERDIFF: rejected hunk from patch%d, cannot diff context\n", + patch_id); + } + } + + /* Record the line offset of the next rej hunk, if any */ + if (read_atatline (line, &rej->off, NULL, NULL, NULL)) + error (EXIT_FAILURE, 0, "line not understood: %s", line); + fseek (rej->fp, next_atat_pos, SEEK_SET); + + if (!feof (rej->fp)) + continue; + +rej_file_eof: + /* Clear out this reject file pointer when it's finished */ + *rej_ptr = NULL; + } + + free (line); +} + +/* `xctx` must come with `num` initialized and `s` and `len` zeroed */ +static void +ctx_lookbehind (const struct line_info *lines, unsigned long start_line_idx, + struct xtra_context *xctx) +{ + unsigned long i, num = 0; + + for (i = start_line_idx - 1; i < start_line_idx; i--) { + const struct line_info *line = &lines[i]; + + if (*line->s == '+') + continue; + + /* Copy out the line and ensure the first character is a space, + * since it may be a minus. */ + xctx->s = xrealloc (xctx->s, xctx->len + line->len); + memmove (xctx->s + line->len, xctx->s, xctx->len); + memcpy (xctx->s, line->s, line->len); + *xctx->s = ' '; + xctx->len += line->len; + + /* Quit when we've got the desired number of context lines */ + if (++num == xctx->num) + return; + } + + /* Record the actual number of extra content lines found, since it is + * less than the number of lines requested. */ + xctx->num = num; +} + +/* `xctx` must come with `num` initialized and `s` and `len` zeroed */ +static void +ctx_lookahead (const char *hunk, size_t hlen, struct xtra_context *xctx) +{ + const char *line, *next_line; + unsigned long num = 0; + size_t linelen; + + /* `hunk` is positioned at the first character of the current line + * parsed by split_patch_hunks(). Reduce it by one first to go to the + * newline character of the previous line, to make our loop simpler. */ + for (line = hunk - 1;; line = next_line) { + /* Go to the character _after_ the newline character */ + line++; + + /* Get the next line now to find the length of the line */ + next_line = memchr (line, '\n', hunk + hlen - line); + if (*line == '+') + continue; + + linelen = next_line + 1 - line; + + /* Copy out the line and ensure the first character is a space, + * since it may be a minus. */ + xctx->s = xrealloc (xctx->s, xctx->len + linelen); + memcpy (xctx->s + xctx->len, line, linelen); + xctx->s[xctx->len] = ' '; + xctx->len += linelen; + + /* Quit when we've got the desired number of context lines */ + if (++num == xctx->num) + break; + + /* Stop when this is the end of the hunk, recording the actual + * number of extra context lines found. */ + if (!next_line || next_line + 1 == hunk + hlen) { + xctx->num = num; + break; + } + } +} + +/* Squash up to max_context*2 unlines between two hunks */ +static int +squash_unline_gap (const char **line_ptr, size_t hlen, const char *unline, + size_t unline_len) +{ + const char *hunk = *line_ptr, *line = hunk, *prev = line; + unsigned int num_unlines = 1; + int squash = 0; + + for (; (line = memchr (line, '\n', hunk + hlen - line)); prev = line) { + /* Go to the character _after_ the newline character */ + line++; + + /* Stop when there's nothing left */ + if (line == hunk + hlen) + break; + + /* Move the line pointer to the last unline in the chunk of up + * to max_context*2 unlines so the loop in split_patch_hunks() + * skips over it and thus skips over the entire unline chunk. */ + if (strncmp (line + 1, unline, unline_len)) { + squash = 1; + break; + } + + if (++num_unlines > max_context * 2) + break; + } + + /* Always advance the line pointer even without squashing */ + *line_ptr = prev; + return squash; +} + +static void +write_xctx (struct xtra_context *xctx, FILE *out) +{ + if (xctx->s) { + fwrite (xctx->s, xctx->len, 1, out); + free (xctx->s); + } +} + +/* Regenerate a patch with the hunks split up to ensure more of the patch gets + * applied successfully. Outputs a `hunk_offs` array (if requested) to map each + * hunk's post-split offset from the original hunk's new line number. + * + * When the unline is provided, that is a hint to strip unlines from context and + * perform splits at unlines in the middle of a hunk. */ +static FILE * +split_patch_hunks (FILE *patch, size_t len, char *file, + unsigned long **hunk_offs, const char *unline) +{ + char *fbuf, *hunk, *next_hunk; + unsigned long hnum = 0; + int has_output = 0; + size_t unline_len; + FILE *out; + + /* Read the patch into a NUL-terminated buffer */ + if (len) { + fbuf = xmalloc (len + 1); + if (fread (fbuf, 1, len, patch) != len) + error (EXIT_FAILURE, errno, "fread() of patch failed"); + } else { + /* The patch is a pipe; we can't seek it, so read until EOF */ + fbuf = NULL; + for (int ch; (ch = fgetc (patch)) != EOF;) { + fbuf = xrealloc (fbuf, ++len + 1); + fbuf[len - 1] = ch; + } + fclose (patch); + } + fbuf[len] = '\0'; + + /* Find the first hunk. `fbuf` is positioned at the start of a line. */ + if (!strncmp (fbuf, "@@ ", 3)) { + hunk = fbuf; + } else { + hunk = strstr (fbuf, "\n@@ "); + if (!hunk) + error (EXIT_FAILURE, 0, "patch file malformed: %s", fbuf); + } + + if (unline) { + /* Create a temporary file for the unline-cleansed output */ + out = xtmpfile (); + + /* Find the length of the unline now to use it in the loop */ + unline_len = strlen (unline); + } else { + /* Create the output file by temporarily modifying `file` */ + strcat (file, ".patch"); + out = xopen (file, "w+"); + file[strlen (file) - strlen (".patch")] = '\0'; + } + + do { + /* nctx[0] = pre-context lines, nctx[1] = post-context lines + * ndelta[0] = deleted lines, ndelta[1] = added lines */ + unsigned long nctx[2] = {}, ndelta[2] = {}, nctx_target; + unsigned long ostart, nstart, orig_nstart, start_line_idx = 0; + struct xtra_context xctx_pre = {}; + struct line_info *lines = NULL; + unsigned long num_lines = 0; + int skipped_lines = 0; + const char *line; + size_t hlen; + + if (read_atatline (hunk, &ostart, NULL, &nstart, NULL)) + error (EXIT_FAILURE, 0, "line not understood: %s", + strsep (&hunk, "\n")); + + /* Save the original hunk's new line number */ + orig_nstart = nstart; + + /* Find the next hunk now to tell where the current hunk ends */ + next_hunk = strstr (hunk, "\n@@ "); + if (next_hunk) + hlen = ++next_hunk - hunk; + else + hlen = strlen (hunk); + + /* Count the number of pre-context and post-context lines in + * this hunk. The greater of the two will be the number of pre- + * context and post-context lines targeted per split hunk. */ + if (!unline) { + unsigned long orig_hunk_nctx[2] = {}; + + for (line = hunk; + (line = memchr (line, '\n', hunk + hlen - line)) && + line[1] == ' '; line++, orig_hunk_nctx[0]++); + for (line = hunk + hlen - 1; + (line = memrchr (hunk, '\n', line - hunk)) && + line[1] == ' '; line--, orig_hunk_nctx[1]++); + nctx_target = MAX (orig_hunk_nctx[0], orig_hunk_nctx[1]); + } + + /* Split this hunk into multiple smaller hunks, if possible. + * This is done by looking for deltas (+/- lines) that aren't + * contiguous and thus have context lines in between them. Note + * that the first line is intentionally skipped because the + * first line is the @@ line. When no splitting occurs, this + * still has the effect of trimming context lines for the hunk + * to ensure the number of pre-context lines and post-context + * lines are equal. */ + for (line = hunk; (line = memchr (line, '\n', hunk + hlen - line));) { + unsigned long start_off = 0, onum, nnum; + struct line_info *start_line, *end_line; + struct xtra_context xctx_post = {}; + size_t hlen_rem; + + /* Go to the character _after_ the newline character */ + line++; + + /* Set the length of the previous line (if any). Only do + * this once because when doing unline splitting, the + * unlines aren't recorded into the lines array. */ + if (lines && !lines[num_lines - 1].len) + lines[num_lines - 1].len = + line - lines[num_lines - 1].s; + + /* Check if this is the end. If so, terminate the hunk + * now because there isn't any new line to parse. */ + hlen_rem = hunk + hlen - line; + if (!hlen_rem) + goto split_hunk_incl_latest; + + /* Check if this is an unline that we need to remove */ + if (unline && !strncmp (line + 1, unline, unline_len)) { + /* Split the hunk now if there's a delta, unless + * this is a bogus hunk from a rejected patch + * hunk. Bogus hunks stem from one side of the + * diff operation consisting only of unlines. + * Such diffs have only unlines in their context + * and only one delta type: either additions or + * subtractions, _not_ both. Discard bogus hunks + * by skipping over them here, which is fine + * since the corresponding rejected patch hunk + * is emitted later. + * + * Sometimes a hunk may appear bogus when it is + * not; this can be identified by checking if + * there are no more than max_context*2 unlines + * until the next hunk. Squash the unlines away + * in that case, which alters the line numbers + * of the hunk as a side effect. The assumption + * is that these two hunks are related to each + * other but are just slightly offset in the two + * diffed files due to small bits of missing + * context that were filled in with unlines. */ + if (ndelta[0] || ndelta[1]) { + if (nctx[0] || nctx[1] || + (ndelta[0] && ndelta[1])) + goto split_hunk_incl_latest; + + if (squash_unline_gap (&line, hlen_rem, + unline, + unline_len)) { + skipped_lines = 1; + continue; + } + } + + /* Move forward the starting line offset, + * discarding any pre-context lines seen. The + * starting line index is set to the _next_ + * (non-unline) line, which may not exist. */ + start_line_idx = num_lines; + start_off += nctx[0] + 1; + nctx[0] = 0; + continue; + } + + /* Record the current line, setting `len` to zero */ + lines = xrealloc (lines, ++num_lines * sizeof (*lines)); + lines[num_lines - 1] = (typeof(*lines)){ line }; + + /* Track +/- lines as well as pre-context and post- + * context lines. Split the hunk upon encountering a +/- + * line after post-context lines, unless we're splitting + * at unlines instead. */ + if (*line == '+' || *line == '-') { + if (!unline && nctx[1]) { + /* The current line belongs to the + * _next_ split hunk. Exclude it. */ + end_line = &lines[num_lines - 2]; + goto split_hunk; + } + + ndelta[*line == '+']++; + } else { + nctx[ndelta[0] || ndelta[1]]++; + } + + /* Keep parsing until there's a need to do a split */ + continue; + +split_hunk_incl_latest: + /* Split the hunk including the latest recorded line */ + end_line = &lines[num_lines - 1]; +split_hunk: + /* Stop now if there are no lines left to make a hunk */ + if (start_line_idx == num_lines) + break; + + /* Check that there's an actual delta recorded */ + if (!ndelta[0] && !ndelta[1]) + error (EXIT_FAILURE, 0, "hunk without +/- lines?"); + + /* Split the current hunk by terminating it and starting + * a new hunk. When generating a patch to apply, there + * must be the same number of pre-context lines as post- + * context lines, otherwise patch will need to fuzz the + * extra context lines. An exception is when the context + * is at either the beginning or end of the file. Target + * having the same number of pre-context and post- + * context lines as the original hunk itself, so the + * user-provided fuzz factor behaves as expected. Note + * that this adjustment impacts ostart and nstart either + * for the current split hunk or the next split hunk. */ + start_line = &lines[start_line_idx]; + if (unline) { + /* Add the start offset to the old/new lines */ + ostart += start_off; + nstart += start_off; + } else if (nctx[1] < nctx_target && hlen_rem) { + /* If the number of post-context lines is still + * below the target number afterwards, then it + * means we hit the end of the original hunk + * itself. It's technically fine because it + * means the original hunk came with an unequal + * number of pre- and post-context lines. */ + xctx_post.num = nctx_target - nctx[1]; + ctx_lookahead (line, hlen_rem, &xctx_post); + } + + /* Calculate the old and new line counts */ + onum = nnum = xctx_pre.num + /* Extra pre-context */ + end_line + 1 - start_line + /* Hunk */ + xctx_post.num; /* Extra post-context */ + onum -= ndelta[1]; + nnum -= ndelta[0]; + + /* Emit the hunk to the output file */ + fprintf (out, "@@ -%lu,%lu +%lu,%lu @@\n", + ostart, onum, nstart, nnum); + write_xctx (&xctx_pre, out); + /* If lines were skipped, then the output needs to be + * written one line at a time. */ + if (skipped_lines) { + skipped_lines = 0; + for (unsigned long i = start_line_idx; + &lines[i] <= end_line; i++) + fwrite (lines[i].s, lines[i].len, 1, out); + } else { + fwrite (start_line->s, + end_line->s + end_line->len - start_line->s, + 1, out); + } + write_xctx (&xctx_post, out); + has_output = 1; + + /* Save the offset from this hunk's original new line */ + if (hunk_offs) { + *hunk_offs = xrealloc (*hunk_offs, ++hnum * + sizeof (*hunk_offs)); + (*hunk_offs)[hnum - 1] = nstart - orig_nstart; + } + + /* Stop when there's nothing left */ + if (!hlen_rem) + break; + + /* Start the next hunk */ + start_line_idx = num_lines; + ostart += onum; + nstart += nnum; + if (unline) { + /* The current line is not included in the next + * hunk when splitting at unlines. */ + nctx[0] = nctx[1] = ndelta[0] = ndelta[1] = 0; + } else { + /* Find extra pre-context if extra post-context + * was used for this split hunk, since it means + * that there isn't enough normal post-context + * to be the next split hunk's pre-context. */ + start_line_idx -= 1 + nctx[1]; + xctx_pre = (typeof(xctx_pre)){ xctx_post.num }; + if (xctx_pre.num) + ctx_lookbehind (lines, start_line_idx, + &xctx_pre); + + /* Subtract the extra post-context lines of this + * hunk, the normal post-context lines of this + * hunk, and the extra pre-context lines for the + * _next_ hunk to get the _next_ hunk's starting + * line numbers. */ + ostart -= xctx_pre.num + xctx_post.num + nctx[1]; + nstart -= xctx_pre.num + xctx_post.num + nctx[1]; + nctx[0] = nctx[1]; + nctx[1] = 0; + ndelta[1] = *line == '+'; + ndelta[0] = !ndelta[1]; + } + } + free (lines); + } while ((hunk = next_hunk)); + free (fbuf); + + /* No output, no party. Can happen if the hunks were only unlines. */ + if (!has_output) { + fclose (out); + return NULL; + } + + /* Reposition the output file back to the beginning */ + rewind (out); + return out; +} + +static int +hunk_info_cmp (const void *lhs_ptr, const void *rhs_ptr) +{ + const struct hunk_info *lhs = lhs_ptr, *rhs = rhs_ptr; + + return lhs->nstart - rhs->nstart; +} + +static int +hunk_reloc_cmp (const void *lhs_ptr, const void *rhs_ptr) +{ + const struct hunk_reloc *lhs = lhs_ptr, *rhs = rhs_ptr; + + return lhs->new - rhs->new; +} + +static void +parse_fuzzed_hunks (FILE *patch_out, const unsigned long *hunk_offs, + struct hunk_reloc **relocs, unsigned long *num_relocs) +{ + char *line = NULL; + size_t linelen; + + /* Parse out each fuzzed hunk's line offset */ + while (getline (&line, &linelen, patch_out) > 0) { + struct hunk_reloc *prev = &(*relocs)[*num_relocs - 1]; + unsigned long fuzz = 0, hnum, lnum; + long off; + + if (sscanf (line, "Hunk #%lu succeeded at %lu (offset %ld", + &hnum, &lnum, &off) != 3 && + sscanf (line, "Hunk #%lu succeeded at %lu with fuzz %lu (offset %ld", + &hnum, &lnum, &fuzz, &off) != 4) + continue; + + /* Recover the correct new line number of the possibly-split + * hunk, and skip it if it matches the relocated new line number + * of the previous hunk (if any). Split hunks are contiguous. */ + lnum -= hunk_offs[hnum - 1]; + if (*relocs && lnum - off == prev->new - prev->off) + continue; + + *relocs = xrealloc (*relocs, ++*num_relocs * sizeof (**relocs)); + (*relocs)[*num_relocs - 1] = + (typeof (**relocs)){ lnum, off, fuzz }; + } + free (line); +} + +static void +fuzzy_relocate_hunks (const char *file, const char *unline, FILE *patch_out, + const unsigned long *hunk_offs) +{ + struct hunk_info *hunks = NULL; + struct hunk_reloc *relocs = NULL; + unsigned long num_hunks = 0, num_relocs = 0; + unsigned long i, j, num_unlines = 0; + char *end, *endl, *fbuf, *start; + int new_hunk = 1; + size_t unlinelen; + struct stat st; + FILE *fp; + + /* Parse the fuzzed hunks when relocating for line offset differences */ + if (patch_out) + parse_fuzzed_hunks (patch_out, hunk_offs, &relocs, &num_relocs); + + /* Open the patched file and copy it into a buffer */ + if (stat (file, &st) < 0) + error (EXIT_FAILURE, errno, "stat() fail"); + fbuf = xmalloc (st.st_size); + fp = xopen (file, "r"); + if (fread (fbuf, 1, st.st_size, fp) != st.st_size) + error (EXIT_FAILURE, errno, "fread() fail"); + fclose (fp); + + /* Sort the relocations array by ascending order of new line number. A + * relocation may indicate that a contiguous block of code should + * actually be split into two or more hunks to better align with the + * other file, since they are split up in the other file. Sorting the + * relocations is needed for tracking this during hunk enumeration. */ + if (relocs) + qsort (relocs, num_relocs, sizeof (*relocs), hunk_reloc_cmp); + + /* Enumerate every hunk in the file */ + start = fbuf; /* Start of the line */ + end = fbuf + st.st_size; /* End of the file */ + unlinelen = strlen(unline); /* Unline length (includes newline char) */ + for (endl = fbuf, i = 1, j = 0; + (endl = memchr (endl, '\n', end - endl)); + start = ++endl, i++) { + size_t len = endl - start + 1; + + /* Cut a new hunk if a relocated hunk starts at this line. This + * is important because a relocated hunk may start in the middle + * of a larger hunk, which is a hint to split the hunk. Note + * that a relocation may occur on an unline, which is corrected + * later on in a different loop. When that is the case, we still + * need to iterate past the relocation at that line in order to + * continue through the relocations array. */ + if (j < num_relocs && i == relocs[j].new) { + j++; + new_hunk = 1; + } + + /* Skip over unlines */ + if (len == unlinelen && !memcmp (start, unline, len)) { + num_unlines++; + new_hunk = 1; + continue; + } + + /* Keep expanding the current detected hunk */ + if (!new_hunk) { + hunks[num_hunks - 1].len += len; + hunks[num_hunks - 1].nend++; + num_unlines = 0; + continue; + } + new_hunk = 0; + + /* Start a new hunk */ + hunks = xrealloc (hunks, ++num_hunks * sizeof (*hunks)); + hunks[num_hunks - 1] = (typeof (*hunks)){ start, len, i, i }; + + /* Check the number of unlines between the end of the previous + * hunk (if any) and the start of the current hunk. If there are + * no more than max_context*2 unlines between the two, then eat + * the unlines and combine the hunks together. Note that we must + * also ignore the relocation for this hunk, if any, while + * accounting for the relocation new line possibly being up to + * `fuzz` lines _before_ the actual line (see more below). */ + if (num_hunks > 1 && num_unlines <= max_context * 2) { + struct hunk_info *hcurr = &hunks[num_hunks - 1]; + struct hunk_info *hprev = hcurr - 1; + + for (int k = num_relocs - 1; k >= 0; k--) { + struct hunk_reloc *rcurr = &relocs[k]; + unsigned long delta; + + if (rcurr->new <= hcurr->nstart) { + delta = hcurr->nstart - rcurr->new; + if (delta <= rcurr->fuzz) + rcurr->ignored = 1; + break; + } + } + + hcurr->nstart = hcurr->nend = hprev->nend + 1; + } + num_unlines = 0; + } + + /* Check and possibly correct the new line number in the case of fuzzed + * hunks. Patch can screw this up and emit a line number up to `fuzz` + * lines _before_ the actual line. */ + for (i = 0; i < num_relocs; i++) { + struct hunk_reloc *rcurr = &relocs[i]; + + /* Skip ignored relocations and relocations without fuzz */ + if (rcurr->ignored || !rcurr->fuzz) + continue; + + for (j = 0; j < num_hunks; j++) { + struct hunk_info *hcurr = &hunks[j]; + unsigned long delta; + + /* Find a hunk that starts within `fuzz` lines after + * this relocation. If it does, correct the new line + * number and the offset to use this hunk. */ + if (hcurr->nstart >= rcurr->new) { + delta = hcurr->nstart - rcurr->new; + if (delta <= rcurr->fuzz) { + rcurr->new += delta; + rcurr->off += delta; + } + break; + } + } + } + + /* Apply relocations */ + for (i = 0; i < num_relocs; i++) { + struct hunk_reloc *rcurr = &relocs[i]; + int found = 0; + + if (rcurr->ignored) + continue; + + for (j = 0; j < num_hunks; j++) { + struct hunk_info *hcurr = &hunks[j], *hprev = hcurr - 1; + + /* Make sure we don't relocate a hunk more than once */ + if (hcurr->relocated) + continue; + + /* Look for the hunk that starts at the new line number, + * subtracting the offset to get the hunk's _original_ + * new line number. And relocate succeeding hunks that + * had their unlines squelched between this hunk. */ + if (hcurr->nstart == rcurr->new || + (found && hcurr->nstart == + hprev->nend + rcurr->off + 1)) { + hcurr->nstart -= rcurr->off; + hcurr->nend -= rcurr->off; + hcurr->relocated = 1; + found = 1; + } else if (found) { + break; + } + } + + /* Fail if we couldn't find the hunk in question */ + if (!found) + error (EXIT_FAILURE, 0, "failed to relocate hunk"); + } + + /* Now that all hunks' final positions are determined, discard hunks + * that overlap with a relocated hunk's new position. Such hunks will + * have generated rejects on the other orig file, which will be emitted + * separately and thus removing the conflicting hunk here won't result + * in any loss of information from the diff. */ + for (i = 0; i < num_hunks; i++) { + /* Find the next relocated hunk */ + if (!hunks[i].relocated) + continue; + + /* Check all non-relocated hunks for conflicts to discard. It is + * possible for there to be more than one conflicting hunk. */ + for (j = 0; j < num_hunks; j++) { + if (hunks[j].relocated || hunks[j].discard) + continue; + + /* Check if hunks[j] starts or ends in hunks[i] */ + if ((hunks[j].nstart >= hunks[i].nstart && + hunks[j].nstart <= hunks[i].nend) || + (hunks[j].nend >= hunks[i].nstart && + hunks[j].nend <= hunks[i].nend)) + hunks[j].discard = 1; + } + } + + /* Sort the hunks by ascending order of starting line number */ + qsort (hunks, num_hunks, sizeof (*hunks), hunk_info_cmp); + + /* Write the final result to the patched file, maintaining the same + * unline. The result (in bytes, not lines) may be smaller than before + * due to some hunks getting discarded and thus replaced by unlines, so + * truncate the entire file before writing. */ + fp = xopen (file, "w+"); + for (i = 0, j = 1; i < num_hunks; i++) { + if (hunks[i].discard) + continue; + + /* Write out unlines between the previous and current hunks */ + for (; j < hunks[i].nstart; j++) + fwrite (unline, unlinelen, 1, fp); + j = hunks[i].nend + 1; + + /* Write out the hunk itself */ + fwrite (hunks[i].s, hunks[i].len, 1, fp); + } + + /* All done, clean everything up */ + fclose (fp); + free (fbuf); + free (hunks); + free (relocs); +} + +static void +fuzzy_do_rej (char *file, struct rej_file *rej, const char *other_file) +{ + char *line = NULL; + size_t linelen; + long atat_pos; + + /* Briefly modify `file` in-place to open the .rej file */ + strcat (file, ".rej"); + rej->fp = xopen (file, "r"); + file[strlen (file) - strlen (".rej")] = '\0'; + + /* Skip (the first two) lines to get to the start of the @@ line */ + do { + atat_pos = ftell (rej->fp); + if (getline (&line, &linelen, rej->fp) <= 0) + error (EXIT_FAILURE, errno, + "Failed to read line from .rej"); + } while (strncmp (line, "@@ ", 3)); + fseek (rej->fp, atat_pos, SEEK_SET); + + /* Export the line offset of the first rej hunk */ + if (read_atatline (line, &rej->off, NULL, NULL, NULL)) + error (EXIT_FAILURE, 0, "line not understood: %s", line); + free (line); + + /* Revert the rejected hunks on the _other_ file, so they're excluded + * from the 'diff' output. Otherwise, 'diff' will output the _reverse_ + * of the rejected hunks, which will muddy the final output as we will + * print out the rejected hunks themselves later anyway. */ + apply_patch (rej->fp, other_file, 1, NULL); + + /* Go back to the @@ after apply_patch() moved the file cursor */ + fseek (rej->fp, atat_pos, SEEK_SET); +} + +static void +fuzzy_cleanup (char *file, int rej) +{ + /* Modify the `file` string in-place */ + char *end = strchr (file, '\0'); + + /* Remove the .rej file if one was generated */ + if (rej) { + strcpy (end, ".rej"); + unlink (file); + } + + /* Remove the .patch file generated from splitting up the hunks */ + strcpy (end, ".patch"); + unlink (file); + + /* Terminate `file` back at where it was terminated originally */ + *end = '\0'; +} + static int output_delta (FILE *p1, FILE *p2, FILE *out) { const char *tmpdir = getenv ("TMPDIR"); unsigned int tmplen; - const char tail1[] = "/interdiff-1.XXXXXX"; - const char tail2[] = "/interdiff-2.XXXXXX"; + /* Reserve space for appending .rej and .patch at the end of tmpp1/2 */ + const char tail1[] = "/interdiff-1.XXXXXX\0patch"; + const char tail2[] = "/interdiff-2.XXXXXX\0patch"; char *tmpp1, *tmpp2; int tmpp1fd, tmpp2fd; struct lines_info file = { NULL, 0, 0, NULL, NULL }; struct lines_info file2 = { NULL, 0, 0, NULL, NULL }; + struct rej_file rej1, rej2; + int ret1 = 0, ret2 = 0; char *oldname = NULL, *newname = NULL; pid_t child; FILE *in; @@ -1234,26 +2200,65 @@ output_delta (FILE *p1, FILE *p2, FILE *out) fseek (p1, pos1, SEEK_SET); fseek (p2, pos2, SEEK_SET); create_orig (p2, &file, 0, NULL); - fseek (p1, pos1, SEEK_SET); - fseek (p2, pos2, SEEK_SET); create_orig (p1, &file2, mode == mode_combine, NULL); - merge_lines(&file, &file2); pos1 = ftell (p1); - - /* Write it out. */ - write_file (&file, tmpp1fd); - write_file (&file, tmpp2fd); - + pos2 = ftell (p2); fseek (p1, start1, SEEK_SET); fseek (p2, start2, SEEK_SET); - if (apply_patch (p1, tmpp1, mode == mode_combine)) - error (EXIT_FAILURE, 0, - "Error applying patch1 to reconstructed file"); + /* Write it out. */ + if (fuzzy) { + /* Ensure the same unline is used for both files */ + write_file (&file, tmpp1fd); + file2.unline = xstrdup (file.unline); + write_file (&file2, tmpp2fd); + } else { + merge_lines (&file, &file2); + write_file (&file, tmpp1fd); + write_file (&file, tmpp2fd); + } - if (apply_patch (p2, tmpp2, 0)) - error (EXIT_FAILURE, 0, - "Error applying patch2 to reconstructed file"); + if (fuzzy) { + unsigned long *hunk_offs = NULL; + FILE *patch_out, *sp; + + /* Split the patch hunks into smaller hunks, then apply that */ + sp = split_patch_hunks (p1, pos1 - start1, tmpp1, &hunk_offs, NULL); + ret1 = apply_patch (sp, tmpp1, false, &patch_out); + fclose (sp); + + /* Relocate hunks in tmpp1 in order to make them align with the + * positions of the hunks in tmpp2. */ + fuzzy_relocate_hunks (tmpp1, file.unline, patch_out, hunk_offs); + fclose (patch_out); + free (hunk_offs); + + /* Split the patch hunks into smaller hunks, then apply that */ + sp = split_patch_hunks (p2, pos2 - start2, tmpp2, NULL, NULL); + ret2 = apply_patch (sp, tmpp2, false, NULL); + fclose (sp); + + /* For tmpp2 relocations, only eat unline gaps between hunks + * that amount to no more than max_context*2 lines. This was + * also done to tmpp1 during its relocation pass. */ + fuzzy_relocate_hunks (tmpp2, file.unline, NULL, NULL); + + /* Handle the rejected hunks. This needs to be done after both + * files are patched because it may revert a rejected hunk from + * the other file. */ + if (ret1) + fuzzy_do_rej (tmpp1, &rej1, tmpp2); + if (ret2) + fuzzy_do_rej (tmpp2, &rej2, tmpp1); + } else { + if (apply_patch (p1, tmpp1, mode == mode_combine, NULL)) + error (EXIT_FAILURE, 0, + "Error applying patch1 to reconstructed file"); + + if (apply_patch (p2, tmpp2, 0, NULL)) + error (EXIT_FAILURE, 0, + "Error applying patch2 to reconstructed file"); + } fseek (p1, pos1, SEEK_SET); @@ -1280,41 +2285,62 @@ output_delta (FILE *p1, FILE *p2, FILE *out) break; } - if (!diff_is_empty) { + /* Rebuild the diff hunks without unlines, since fuzzy diffing shows + * context line differences that therefore may cause unlines to appear + * in the diff output. We don't want any unlines in the final output. */ + if (fuzzy && !diff_is_empty) { + in = split_patch_hunks (in, 0, NULL, NULL, file.unline); + diff_is_empty = !in; + } + + if (!diff_is_empty || ret1 || ret2) { + /* Initialize the rej pointers for output_rej_hunks() */ + struct rej_file *rej1_ptr = ret1 ? &rej1 : NULL; + struct rej_file *rej2_ptr = ret2 ? &rej2 : NULL; /* ANOTHER temporary file! This is to catch the case * where we just don't have enough context to generate * a proper interdiff. */ FILE *tmpdiff = xtmpfile (); - char *line = NULL; - size_t linelen; - for (;;) { - ssize_t got = getline (&line, &linelen, in); - if (got < 0) - break; - fwrite (line, (size_t) got, 1, tmpdiff); - if (*line != ' ' && !strcmp (line + 1, file.unline)) { - /* Uh-oh. We're trying to output a - * line that made up (we never saw the - * original). As long as this is at - * the end of a hunk we can safely - * drop it (done in trim_context - * later). */ - got = getline (&line, &linelen, in); + + if (!diff_is_empty) { + char *line = NULL; + size_t linelen; + for (;;) { + ssize_t got = getline (&line, &linelen, in); if (got < 0) - continue; - else if (strncmp (line, "@@ ", 3)) { - /* An interdiff isn't possible. - * Evasive action: just revert the - * original and copy the new - * version. */ - fclose (tmpdiff); - free (line); - goto evasive_action; - } + break; + /* Output fuzzy diff reject hunks in order */ + output_rej_hunks (line, &rej1_ptr, &rej2_ptr, + tmpdiff); fwrite (line, (size_t) got, 1, tmpdiff); + if (*line != ' ' && + !strcmp (line + 1, file.unline)) { + /* Uh-oh. We're trying to output a + * line that made up (we never saw the + * original). As long as this is at + * the end of a hunk we can safely + * drop it (done in trim_context + * later). */ + got = getline (&line, &linelen, in); + if (got < 0) + continue; + else if (strncmp (line, "@@ ", 3)) { + /* An interdiff isn't possible. + * Evasive action: just revert + * the original and copy the new + * version. */ + fclose (tmpdiff); + free (line); + goto evasive_action; + } + fwrite (line, (size_t) got, 1, tmpdiff); + } } + free (line); } - free (line); + + /* Output any remaining reject hunks */ + output_rej_hunks (NULL, &rej1_ptr, &rej2_ptr, tmpdiff); /* First character */ if (human_readable) { @@ -1342,13 +2368,18 @@ output_delta (FILE *p1, FILE *p2, FILE *out) fclose (tmpdiff); } - fclose (in); + if (in) + fclose (in); waitpid (child, NULL, 0); if (debug) printf ("reconstructed orig1=%s orig2=%s\n", tmpp1, tmpp2); else { unlink (tmpp1); unlink (tmpp2); + if (fuzzy) { + fuzzy_cleanup (tmpp1, ret1); + fuzzy_cleanup (tmpp2, ret2); + } } free (oldname); free (newname); @@ -1361,6 +2392,10 @@ output_delta (FILE *p1, FILE *p2, FILE *out) else { unlink (tmpp1); unlink (tmpp2); + if (fuzzy) { + fuzzy_cleanup (tmpp1, ret1); + fuzzy_cleanup (tmpp2, ret2); + } } if (human_readable) fprintf (out, "%s impossible; taking evasive action\n", @@ -1442,29 +2477,35 @@ index_patch_generic (FILE *patch_file, struct file_list **file_list, int need_sk /* For patch2, we need to handle the @@ line and skip content */ if (need_skip_content) { - if (getline (&line, &linelen, patch_file) == -1) { + int found = 0; + + while (!found && + getline (&line, &linelen, patch_file) > 0) { + if (strncmp (line, "@@ ", 3)) + continue; + + p = strchr (line + 3, '+'); + if (!p) + continue; + p = strchr (p, ','); + if (p) { + /* Like '@@ -1,3 +1,3 @@' */ + p++; + skip = strtoul (p, &end, 10); + if (p == end) + continue; + } else + /* Like '@@ -1 +1 @@' */ + skip = 1; + found = 1; + } + + if (!found) { free (names[0]); free (names[1]); break; } - if (strncmp (line, "@@ ", 3)) - goto try_next; - - p = strchr (line + 3, '+'); - if (!p) - goto try_next; - p = strchr (p, ','); - if (p) { - /* Like '@@ -1,3 +1,3 @@' */ - p++; - skip = strtoul (p, &end, 10); - if (p == end) - goto try_next; - } else - /* Like '@@ -1 +1 @@' */ - skip = 1; - add_to_list (file_list, best_name (2, names), pos); while (skip--) { @@ -1480,7 +2521,6 @@ index_patch_generic (FILE *patch_file, struct file_list **file_list, int need_sk add_to_list (file_list, best_name (2, names), pos); } - try_next: free (names[0]); free (names[1]); } @@ -1809,7 +2849,7 @@ flipdiff (FILE *p1, FILE *p2, FILE *flip1, FILE *flip2) tmpfd = xmkstemp (tmpp1); write_file (&intermediate, tmpfd); fsetpos (p1, &at1); - if (apply_patch (p1, tmpp1, 1)) + if (apply_patch (p1, tmpp1, 1, NULL)) error (EXIT_FAILURE, 0, "Error reconstructing original file"); @@ -1818,7 +2858,7 @@ flipdiff (FILE *p1, FILE *p2, FILE *flip1, FILE *flip2) tmpfd = xmkstemp (tmpp3); write_file (&intermediate, tmpfd); fsetpos (p2, &at2); - if (apply_patch (p2, tmpp3, 0)) + if (apply_patch (p2, tmpp3, 0, NULL)) error (EXIT_FAILURE, 0, "Error reconstructing final file"); @@ -2220,7 +3260,12 @@ syntax (int err) " (interdiff) When a patch from patch1 is not in patch2,\n" " don't revert it\n" " --in-place (flipdiff) Write the output to the original input\n" -" files\n"; +" files\n" +" --fuzzy[=N]\n" +" (interdiff) Perform a fuzzy comparison, showing the minimal\n" +" set of differences including those in context lines.\n" +" Optionally set N to the maximum number of context lines\n" +" to fuzz (which passes '--fuzz=N' to the patch utility).\n"; fprintf (err ? stderr : stdout, syntax_str, progname, progname); exit (err); @@ -2282,6 +3327,7 @@ main (int argc, char *argv[]) {"flip", 0, 0, 1000 + 'F' }, {"no-revert-omitted", 0, 0, 1000 + 'R' }, {"in-place", 0, 0, 1000 + 'i' }, + {"fuzzy", 2, 0, 1000 + 'f' }, {"debug", 0, 0, 1000 + 'D' }, {"strip-match", 1, 0, 'p'}, {"unified", 1, 0, 'U'}, @@ -2369,6 +3415,16 @@ main (int argc, char *argv[]) syntax (1); flipdiff_inplace = 1; break; + case 1000 + 'f': + if (mode != mode_inter) + syntax (1); + if (optarg) { + max_fuzz_user = strtoul (optarg, &end, 0); + if (optarg == end) + syntax (1); + } + fuzzy = 1; + break; case 1000 + 'D': debug = 1; break; diff --git a/tests/fuzzy1/run-test b/tests/fuzzy1/run-test new file mode 100755 index 0000000..f206c93 --- /dev/null +++ b/tests/fuzzy1/run-test @@ -0,0 +1,74 @@ +#!/bin/sh + +# This is an interdiff(1) testcase. +# Test: Fuzzy diffing with one rejected hunk per patched file. + + +. ${top_srcdir-.}/tests/common.sh + +cat << 'EOF' > patch1 +--- file ++++ file +@@ -1,4 +1,4 @@ +-line 1 ++LINE 1 + line 2 + line 3 + line 4 +EOF + +cat << 'EOF' > patch2 +--- file ++++ file +@@ -5,9 +5,6 @@ + line 5 + if + 1 +-fi +-if +-2 + fi + A + B +EOF + +cat << 'EOF' > expected +diff -u file file +--- file ++++ file +@@ -1,4 +1,4 @@ INTERDIFF: rejected hunk from patch1, cannot diff context +-line 1 ++LINE 1 + line 2 + line 3 + line 4 +@@ -1,9 +1,4 @@ +-line 5 +-if +-1 +-fi +-if +-2 +-fi +-A +-B ++line 1 ++line 2 ++line 3 ++line 4 +@@ -5,9 +5,6 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + line 5 + if + 1 +-fi +-if +-2 + fi + A + B +EOF + +${INTERDIFF} --fuzzy patch1 patch2 2>errors >output +[ -s errors ] && exit 1 + +cmp output expected || exit 1 diff --git a/tests/fuzzy2/run-test b/tests/fuzzy2/run-test new file mode 100755 index 0000000..8d41cd8 --- /dev/null +++ b/tests/fuzzy2/run-test @@ -0,0 +1,42 @@ +#!/bin/sh + +# This is an interdiff(1) testcase. +# Test: Fuzzy diffing with line offsets successfully fuzzed. + + +. ${top_srcdir-.}/tests/common.sh + +cat << 'EOF' > patch1 +--- file ++++ file +@@ -5,9 +5,6 @@ + line 5 + if + 1 +-fi +-if +-2 + fi + A + B +EOF + +cat << 'EOF' > patch2 +--- file ++++ file +@@ -50,9 +50,6 @@ + line 5 + if + 1 +-fi +-if +-2 + fi + A + B +EOF + +${INTERDIFF} --fuzzy patch1 patch2 2>errors >output +[ -s errors ] && exit 1 +[ -s output ] && exit 1 +exit 0 diff --git a/tests/fuzzy3/run-test b/tests/fuzzy3/run-test new file mode 100755 index 0000000..d763a1e --- /dev/null +++ b/tests/fuzzy3/run-test @@ -0,0 +1,57 @@ +#!/bin/sh + +# This is an interdiff(1) testcase. +# Test: Fuzzy diffing with differing context lines and line offsets fuzzed. + + +. ${top_srcdir-.}/tests/common.sh + +cat << 'EOF' > patch1 +--- file ++++ file +@@ -5,9 +5,6 @@ + line 5 + if + 1 +-fi +-if +-2 + fi + A + B +EOF + +cat << 'EOF' > patch2 +--- file ++++ file +@@ -50,9 +50,6 @@ + line 6 + if + 1 +-fi +-if +-2 + fi + B + C +EOF + +cat << 'EOF' > expected +diff -u file file +--- file ++++ file +@@ -2,6 +2,6 @@ +-line 6 ++line 5 + if + 1 + fi ++A + B +-C +EOF + +${INTERDIFF} --fuzzy patch1 patch2 2>errors >output +[ -s errors ] && exit 1 + +cmp output expected || exit 1 diff --git a/tests/fuzzy4/run-test b/tests/fuzzy4/run-test new file mode 100755 index 0000000..3f33783 --- /dev/null +++ b/tests/fuzzy4/run-test @@ -0,0 +1,82 @@ +#!/bin/sh + +# This is an interdiff(1) testcase. +# Test: Fuzzy diffing test of the optional N argument to --fuzzy. Triggers +# rejects by setting the fuzz value to 1, when it could've been fuzzed with the +# default fuzz value of 2 in `patch`. + + +. ${top_srcdir-.}/tests/common.sh + +cat << 'EOF' > patch1 +--- file ++++ file +@@ -5,9 +5,6 @@ + line 5 + if + 1 +-fi +-if +-2 + fi + A + B +EOF + +cat << 'EOF' > patch2 +--- file ++++ file +@@ -50,9 +50,6 @@ + line 6 + if + 1 +-fi +-if +-2 + fi + B + C +EOF + +cat << 'EOF' > expected +diff -u file file +--- file ++++ file +@@ -2,9 +2,9 @@ +-line 6 ++line 5 + if + 1 + fi + if + 2 + fi ++A + B +-C +@@ -5,9 +5,6 @@ INTERDIFF: rejected hunk from patch1, cannot diff context + line 5 + if + 1 +-fi +-if +-2 + fi + A + B +@@ -50,9 +50,6 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + line 6 + if + 1 +-fi +-if +-2 + fi + B + C +EOF + +${INTERDIFF} --fuzzy=1 patch1 patch2 2>errors >output +[ -s errors ] && exit 1 + +cmp output expected || exit 1 diff --git a/tests/fuzzy5/run-test b/tests/fuzzy5/run-test new file mode 100755 index 0000000..f27b3b9 --- /dev/null +++ b/tests/fuzzy5/run-test @@ -0,0 +1,122 @@ +#!/bin/sh + +# This is an interdiff(1) testcase. +# Test: Fuzzy diffing with more than one rejected hunk per patched file. This +# also tests the hunk parser by inserting whitespace between the +++ line and +# the first hunk, which should be gracefully ignored. + + +. ${top_srcdir-.}/tests/common.sh + +cat << 'EOF' > patch1 +--- file ++++ file + + + +@@ -5,9 +5,6 @@ + line 5 + if + 1 +-fi +-if +-2 + fi + A + B +@@ -7,7 +7,7 @@ + C + 9 + 8 +-1 ++7 + D + E + if +EOF + +cat << 'EOF' > patch2 +--- file ++++ file + +@@ -50,9 +50,6 @@ + line 6 + if + 1 +-fi +-if +-2 + fi + B + C +@@ -7,7 +7,7 @@ + D + Z + 1 +-1 ++7 + F + E + if +EOF + +cat << 'EOF' > expected +diff -u file file +--- file ++++ file +@@ -2,9 +2,2 @@ +-line 6 ++line 5 + if + 1 + fi + if + 2 + fi ++A + B +-C +@@ -5,9 +5,6 @@ INTERDIFF: rejected hunk from patch1, cannot diff context + line 5 + if + 1 +-fi +-if +-2 + fi + A + B +@@ -7,7 +7,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context + C + 9 + 8 +-1 ++7 + D + E + if +@@ -50,9 +50,6 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + line 6 + if + 1 +-fi +-if +-2 + fi + B + C +@@ -7,7 +7,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + D + Z + 1 +-1 ++7 + F + E + if +EOF + +${INTERDIFF} --fuzzy=1 patch1 patch2 2>errors >output +[ -s errors ] && exit 1 + +cmp output expected || exit 1 diff --git a/tests/fuzzy6/run-test b/tests/fuzzy6/run-test new file mode 100755 index 0000000..f8091c8 --- /dev/null +++ b/tests/fuzzy6/run-test @@ -0,0 +1,58 @@ +#!/bin/sh + +# This is an interdiff(1) testcase. +# Test: Fuzzy diffing with the hunk splitter stressed by having a +/- line as +# either the first or last line in a hunk, while triggering a split by +# separating two deltas by some context lines. This also tests patches with an +# unequal number of pre-context and post-context lines. + + +. ${top_srcdir-.}/tests/common.sh + +cat << 'EOF' > patch1 +--- file ++++ file +@@ -5,2 +5,2 @@ +-line 5 + if ++1 +EOF + +cat << 'EOF' > patch2 +--- file ++++ file +@@ -50,5 +50,5 @@ + hi + line 4 +-line 5 + if ++1 + 2 +EOF + +cat << 'EOF' > expected +diff -u file file +--- file ++++ file +@@ -2,5 +2,2 @@ +-hi +-line 4 + line 5 + if +-2 +@@ -50,5 +50,4 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + hi + line 4 +-line 5 + if + 2 +@@ -53,2 +52,3 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + if ++1 + 2 +EOF + +${INTERDIFF} --fuzzy=1 patch1 patch2 2>errors >output +[ -s errors ] && exit 1 + +cmp output expected || exit 1 diff --git a/tests/fuzzy7/run-test b/tests/fuzzy7/run-test new file mode 100755 index 0000000..06b9d17 --- /dev/null +++ b/tests/fuzzy7/run-test @@ -0,0 +1,945 @@ +#!/bin/sh + +# This is an interdiff(1) testcase. +# Test: Fuzzy diffing with a real Linux kernel backport compared against its +# upstream version. Stresses having multiple relocations and most of the fuzzy +# diffing machinery as a whole. + + +. ${top_srcdir-.}/tests/common.sh + +cat << 'EOF' > patch1 +From ed0708f971a26619964eea4e9eccb73847caa0c7 Mon Sep 17 00:00:00 2001 +From: Shreeya Patel +Date: Wed, 3 Sep 2025 12:36:41 +0000 +Subject: [PATCH] net: mana: Handle Reset Request from MANA NIC + +jira LE-3923 +commit-author Haiyang Zhang +commit fbe346ce9d626680a4dd0f079e17c7b5dd32ffad +upstream-diff There were conflicts seen when applying this +patch due to the following missing commits :- +ca8ac489ca33 ("net: mana: Handle unsupported HWC commands") +505cc26bcae0 ("net: mana: Add support for auxiliary device servicing +events") + +Upon receiving the Reset Request, pause the connection and clean up +queues, wait for the specified period, then resume the NIC. +In the cleanup phase, the HWC is no longer responding, so set hwc_timeout +to zero to skip waiting on the response. + + Signed-off-by: Haiyang Zhang +Link: https://patch.msgid.link/1751055983-29760-1-git-send-email-haiyangz@linux.microsoft.com + Signed-off-by: Jakub Kicinski +(cherry picked from commit fbe346ce9d626680a4dd0f079e17c7b5dd32ffad) + Signed-off-by: Shreeya Patel +--- + .../net/ethernet/microsoft/mana/gdma_main.c | 127 ++++++++++++++---- + .../net/ethernet/microsoft/mana/hw_channel.c | 4 +- + drivers/net/ethernet/microsoft/mana/mana_en.c | 37 +++-- + include/net/mana/gdma.h | 10 ++ + 4 files changed, 143 insertions(+), 35 deletions(-) + +diff --git a/drivers/net/ethernet/microsoft/mana/gdma_main.c b/drivers/net/ethernet/microsoft/mana/gdma_main.c +index 9d19df7cd82b3..cf3b920476cf6 100644 +--- a/drivers/net/ethernet/microsoft/mana/gdma_main.c ++++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c +@@ -8,6 +8,7 @@ + #include + + #include ++#include + + #include + struct dentry *mana_debugfs_root; +@@ -64,6 +65,24 @@ static void mana_gd_init_registers(struct pci_dev *pdev) + mana_gd_init_vf_regs(pdev); + } + ++/* Suppress logging when we set timeout to zeo */ ++bool mana_need_log(struct gdma_context *gc, int err) ++{ ++ struct hw_channel_context *hwc; ++ ++ if (err != -ETIMEDOUT) ++ return true; ++ ++ if (!gc) ++ return true; ++ ++ hwc = gc->hwc.driver_data; ++ if (hwc && hwc->hwc_timeout == 0) ++ return false; ++ ++ return true; ++} ++ + static int mana_gd_query_max_resources(struct pci_dev *pdev) + { + struct gdma_context *gc = pci_get_drvdata(pdev); +@@ -267,8 +286,9 @@ static int mana_gd_disable_queue(struct gdma_queue *queue) + + err = mana_gd_send_request(gc, sizeof(req), &req, sizeof(resp), &resp); + if (err || resp.hdr.status) { +- dev_err(gc->dev, "Failed to disable queue: %d, 0x%x\n", err, +- resp.hdr.status); ++ if (mana_need_log(gc, err)) ++ dev_err(gc->dev, "Failed to disable queue: %d, 0x%x\n", err, ++ resp.hdr.status); + return err ? err : -EPROTO; + } + +@@ -353,25 +373,12 @@ void mana_gd_ring_cq(struct gdma_queue *cq, u8 arm_bit) + + #define MANA_SERVICE_PERIOD 10 + +-struct mana_serv_work { +- struct work_struct serv_work; +- struct pci_dev *pdev; +-}; +- +-static void mana_serv_func(struct work_struct *w) ++static void mana_serv_fpga(struct pci_dev *pdev) + { +- struct mana_serv_work *mns_wk; + struct pci_bus *bus, *parent; +- struct pci_dev *pdev; +- +- mns_wk = container_of(w, struct mana_serv_work, serv_work); +- pdev = mns_wk->pdev; + + pci_lock_rescan_remove(); + +- if (!pdev) +- goto out; +- + bus = pdev->bus; + if (!bus) { + dev_err(&pdev->dev, "MANA service: no bus\n"); +@@ -392,7 +399,74 @@ static void mana_serv_func(struct work_struct *w) + + out: + pci_unlock_rescan_remove(); ++} ++ ++static void mana_serv_reset(struct pci_dev *pdev) ++{ ++ struct gdma_context *gc = pci_get_drvdata(pdev); ++ struct hw_channel_context *hwc; ++ ++ if (!gc) { ++ dev_err(&pdev->dev, "MANA service: no GC\n"); ++ return; ++ } ++ ++ hwc = gc->hwc.driver_data; ++ if (!hwc) { ++ dev_err(&pdev->dev, "MANA service: no HWC\n"); ++ goto out; ++ } ++ ++ /* HWC is not responding in this case, so don't wait */ ++ hwc->hwc_timeout = 0; ++ ++ dev_info(&pdev->dev, "MANA reset cycle start\n"); + ++ mana_gd_suspend(pdev, PMSG_SUSPEND); ++ ++ msleep(MANA_SERVICE_PERIOD * 1000); ++ ++ mana_gd_resume(pdev); ++ ++ dev_info(&pdev->dev, "MANA reset cycle completed\n"); ++ ++out: ++ gc->in_service = false; ++} ++ ++struct mana_serv_work { ++ struct work_struct serv_work; ++ struct pci_dev *pdev; ++ enum gdma_eqe_type type; ++}; ++ ++static void mana_serv_func(struct work_struct *w) ++{ ++ struct mana_serv_work *mns_wk; ++ struct pci_dev *pdev; ++ ++ mns_wk = container_of(w, struct mana_serv_work, serv_work); ++ pdev = mns_wk->pdev; ++ ++ if (!pdev) ++ goto out; ++ ++ switch (mns_wk->type) { ++ case GDMA_EQE_HWC_FPGA_RECONFIG: ++ mana_serv_fpga(pdev); ++ break; ++ ++ case GDMA_EQE_HWC_RESET_REQUEST: ++ mana_serv_reset(pdev); ++ break; ++ ++ default: ++ dev_err(&pdev->dev, "MANA service: unknown type %d\n", ++ mns_wk->type); ++ break; ++ } ++ ++out: + pci_dev_put(pdev); + kfree(mns_wk); + module_put(THIS_MODULE); +@@ -448,6 +522,7 @@ static void mana_gd_process_eqe(struct gdma_queue *eq) + break; + + case GDMA_EQE_HWC_FPGA_RECONFIG: ++ case GDMA_EQE_HWC_RESET_REQUEST: + dev_info(gc->dev, "Recv MANA service type:%d\n", type); + + if (gc->in_service) { +@@ -469,6 +544,7 @@ static void mana_gd_process_eqe(struct gdma_queue *eq) + dev_info(gc->dev, "Start MANA service type:%d\n", type); + gc->in_service = true; + mns_wk->pdev = to_pci_dev(gc->dev); ++ mns_wk->type = type; + pci_dev_get(mns_wk->pdev); + INIT_WORK(&mns_wk->serv_work, mana_serv_func); + schedule_work(&mns_wk->serv_work); +@@ -615,7 +691,8 @@ int mana_gd_test_eq(struct gdma_context *gc, struct gdma_queue *eq) + + err = mana_gd_send_request(gc, sizeof(req), &req, sizeof(resp), &resp); + if (err) { +- dev_err(dev, "test_eq failed: %d\n", err); ++ if (mana_need_log(gc, err)) ++ dev_err(dev, "test_eq failed: %d\n", err); + goto out; + } + +@@ -650,7 +727,7 @@ static void mana_gd_destroy_eq(struct gdma_context *gc, bool flush_evenets, + + if (flush_evenets) { + err = mana_gd_test_eq(gc, queue); +- if (err) ++ if (err && mana_need_log(gc, err)) + dev_warn(gc->dev, "Failed to flush EQ: %d\n", err); + } + +@@ -796,8 +873,9 @@ int mana_gd_destroy_dma_region(struct gdma_context *gc, u64 dma_region_handle) + + err = mana_gd_send_request(gc, sizeof(req), &req, sizeof(resp), &resp); + if (err || resp.hdr.status) { +- dev_err(gc->dev, "Failed to destroy DMA region: %d, 0x%x\n", +- err, resp.hdr.status); ++ if (mana_need_log(gc, err)) ++ dev_err(gc->dev, "Failed to destroy DMA region: %d, 0x%x\n", ++ err, resp.hdr.status); + return -EPROTO; + } + +@@ -1096,8 +1174,9 @@ int mana_gd_deregister_device(struct gdma_dev *gd) + + err = mana_gd_send_request(gc, sizeof(req), &req, sizeof(resp), &resp); + if (err || resp.hdr.status) { +- dev_err(gc->dev, "Failed to deregister device: %d, 0x%x\n", +- err, resp.hdr.status); ++ if (mana_need_log(gc, err)) ++ dev_err(gc->dev, "Failed to deregister device: %d, 0x%x\n", ++ err, resp.hdr.status); + if (!err) + err = -EPROTO; + } +@@ -1697,7 +1776,7 @@ static void mana_gd_remove(struct pci_dev *pdev) + } + + /* The 'state' parameter is not used. */ +-static int mana_gd_suspend(struct pci_dev *pdev, pm_message_t state) ++int mana_gd_suspend(struct pci_dev *pdev, pm_message_t state) + { + struct gdma_context *gc = pci_get_drvdata(pdev); + +@@ -1712,7 +1791,7 @@ static int mana_gd_suspend(struct pci_dev *pdev, pm_message_t state) + * fail -- if this happens, it's safer to just report an error than try to undo + * what has been done. + */ +-static int mana_gd_resume(struct pci_dev *pdev) ++int mana_gd_resume(struct pci_dev *pdev) + { + struct gdma_context *gc = pci_get_drvdata(pdev); + int err; +diff --git a/drivers/net/ethernet/microsoft/mana/hw_channel.c b/drivers/net/ethernet/microsoft/mana/hw_channel.c +index 4291a2fc2710e..aed60be5ee389 100644 +--- a/drivers/net/ethernet/microsoft/mana/hw_channel.c ++++ b/drivers/net/ethernet/microsoft/mana/hw_channel.c +@@ -854,7 +854,9 @@ int mana_hwc_send_request(struct hw_channel_context *hwc, u32 req_len, + + if (!wait_for_completion_timeout(&ctx->comp_event, + (msecs_to_jiffies(hwc->hwc_timeout)))) { +- dev_err(hwc->dev, "HWC: Request timed out!\n"); ++ if (hwc->hwc_timeout != 0) ++ dev_err(hwc->dev, "HWC: Request timed out!\n"); ++ + err = -ETIMEDOUT; + goto out; + } +diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c +index cbecacf503422..acf1342536463 100644 +--- a/drivers/net/ethernet/microsoft/mana/mana_en.c ++++ b/drivers/net/ethernet/microsoft/mana/mana_en.c +@@ -45,6 +45,15 @@ static const struct file_operations mana_dbg_q_fops = { + .read = mana_dbg_q_read, + }; + ++static bool mana_en_need_log(struct mana_port_context *apc, int err) ++{ ++ if (apc && apc->ac && apc->ac->gdma_dev && ++ apc->ac->gdma_dev->gdma_context) ++ return mana_need_log(apc->ac->gdma_dev->gdma_context, err); ++ else ++ return true; ++} ++ + /* Microsoft Azure Network Adapter (MANA) functions */ + + static int mana_open(struct net_device *ndev) +@@ -768,7 +777,8 @@ static int mana_send_request(struct mana_context *ac, void *in_buf, + err = mana_gd_send_request(gc, in_len, in_buf, out_len, + out_buf); + if (err || resp->status) { +- if (req->req.msg_type != MANA_QUERY_PHY_STAT) ++ if (req->req.msg_type != MANA_QUERY_PHY_STAT && ++ mana_need_log(gc, err)) + dev_err(dev, "Failed to send mana message: %d, 0x%x\n", + err, resp->status); + return err ? err : -EPROTO; +@@ -845,8 +855,10 @@ static void mana_pf_deregister_hw_vport(struct mana_port_context *apc) + err = mana_send_request(apc->ac, &req, sizeof(req), &resp, + sizeof(resp)); + if (err) { +- netdev_err(apc->ndev, "Failed to unregister hw vPort: %d\n", +- err); ++ if (mana_en_need_log(apc, err)) ++ netdev_err(apc->ndev, "Failed to unregister hw vPort: %d\n", ++ err); ++ + return; + } + +@@ -901,8 +913,10 @@ static void mana_pf_deregister_filter(struct mana_port_context *apc) + err = mana_send_request(apc->ac, &req, sizeof(req), &resp, + sizeof(resp)); + if (err) { +- netdev_err(apc->ndev, "Failed to unregister filter: %d\n", +- err); ++ if (mana_en_need_log(apc, err)) ++ netdev_err(apc->ndev, "Failed to unregister filter: %d\n", ++ err); ++ + return; + } + +@@ -1132,7 +1146,9 @@ static int mana_cfg_vport_steering(struct mana_port_context *apc, + err = mana_send_request(apc->ac, req, req_buf_size, &resp, + sizeof(resp)); + if (err) { +- netdev_err(ndev, "Failed to configure vPort RX: %d\n", err); ++ if (mana_en_need_log(apc, err)) ++ netdev_err(ndev, "Failed to configure vPort RX: %d\n", err); ++ + goto out; + } + +@@ -1227,7 +1243,9 @@ void mana_destroy_wq_obj(struct mana_port_context *apc, u32 wq_type, + err = mana_send_request(apc->ac, &req, sizeof(req), &resp, + sizeof(resp)); + if (err) { +- netdev_err(ndev, "Failed to destroy WQ object: %d\n", err); ++ if (mana_en_need_log(apc, err)) ++ netdev_err(ndev, "Failed to destroy WQ object: %d\n", err); ++ + return; + } + +@@ -2872,11 +2890,10 @@ static int mana_dealloc_queues(struct net_device *ndev) + + apc->rss_state = TRI_STATE_FALSE; + err = mana_config_rss(apc, TRI_STATE_FALSE, false, false); +- if (err) { ++ if (err && mana_en_need_log(apc, err)) + netdev_err(ndev, "Failed to disable vPort: %d\n", err); +- return err; +- } + ++ /* Even in err case, still need to cleanup the vPort */ + mana_destroy_vport(apc); + + return 0; +diff --git a/include/net/mana/gdma.h b/include/net/mana/gdma.h +index b602b2e55939c..af5596bf46878 100644 +--- a/include/net/mana/gdma.h ++++ b/include/net/mana/gdma.h +@@ -60,6 +60,7 @@ enum gdma_eqe_type { + GDMA_EQE_HWC_INIT_DONE = 131, + GDMA_EQE_HWC_FPGA_RECONFIG = 132, + GDMA_EQE_HWC_SOC_RECONFIG_DATA = 133, ++ GDMA_EQE_HWC_RESET_REQUEST = 135, + GDMA_EQE_RNIC_QP_FATAL = 176, + }; + +@@ -559,6 +560,9 @@ enum { + /* Driver can handle holes (zeros) in the device list */ + #define GDMA_DRV_CAP_FLAG_1_DEV_LIST_HOLES_SUP BIT(11) + ++/* Driver can self reset on EQE notification */ ++#define GDMA_DRV_CAP_FLAG_1_SELF_RESET_ON_EQE BIT(14) ++ + /* Driver can self reset on FPGA Reconfig EQE notification */ + #define GDMA_DRV_CAP_FLAG_1_HANDLE_RECONFIG_EQE BIT(17) + +@@ -568,6 +572,7 @@ enum { + GDMA_DRV_CAP_FLAG_1_HWC_TIMEOUT_RECONFIG | \ + GDMA_DRV_CAP_FLAG_1_VARIABLE_INDIRECTION_TABLE_SUPPORT | \ + GDMA_DRV_CAP_FLAG_1_DEV_LIST_HOLES_SUP | \ ++ GDMA_DRV_CAP_FLAG_1_SELF_RESET_ON_EQE | \ + GDMA_DRV_CAP_FLAG_1_HANDLE_RECONFIG_EQE) + + #define GDMA_DRV_CAP_FLAGS2 0 +@@ -892,4 +897,9 @@ int mana_gd_destroy_dma_region(struct gdma_context *gc, u64 dma_region_handle); + void mana_register_debugfs(void); + void mana_unregister_debugfs(void); + ++int mana_gd_suspend(struct pci_dev *pdev, pm_message_t state); ++int mana_gd_resume(struct pci_dev *pdev); ++ ++bool mana_need_log(struct gdma_context *gc, int err); ++ + #endif /* _GDMA_H */ +-- +2.51.2 + +EOF + +cat << 'EOF' > patch2 +From fbe346ce9d626680a4dd0f079e17c7b5dd32ffad Mon Sep 17 00:00:00 2001 +From: Haiyang Zhang +Date: Fri, 27 Jun 2025 13:26:23 -0700 +Subject: [PATCH] net: mana: Handle Reset Request from MANA NIC + +Upon receiving the Reset Request, pause the connection and clean up +queues, wait for the specified period, then resume the NIC. +In the cleanup phase, the HWC is no longer responding, so set hwc_timeout +to zero to skip waiting on the response. + +Signed-off-by: Haiyang Zhang +Link: https://patch.msgid.link/1751055983-29760-1-git-send-email-haiyangz@linux.microsoft.com +Signed-off-by: Jakub Kicinski +--- + .../net/ethernet/microsoft/mana/gdma_main.c | 127 ++++++++++++++---- + .../net/ethernet/microsoft/mana/hw_channel.c | 4 +- + drivers/net/ethernet/microsoft/mana/mana_en.c | 37 +++-- + include/net/mana/gdma.h | 10 ++ + 4 files changed, 143 insertions(+), 35 deletions(-) + +diff --git a/drivers/net/ethernet/microsoft/mana/gdma_main.c b/drivers/net/ethernet/microsoft/mana/gdma_main.c +index 55dd7dee718cc..a468cd8e5f361 100644 +--- a/drivers/net/ethernet/microsoft/mana/gdma_main.c ++++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c +@@ -10,6 +10,7 @@ + #include + + #include ++#include + + struct dentry *mana_debugfs_root; + +@@ -68,6 +69,24 @@ static void mana_gd_init_registers(struct pci_dev *pdev) + mana_gd_init_vf_regs(pdev); + } + ++/* Suppress logging when we set timeout to zero */ ++bool mana_need_log(struct gdma_context *gc, int err) ++{ ++ struct hw_channel_context *hwc; ++ ++ if (err != -ETIMEDOUT) ++ return true; ++ ++ if (!gc) ++ return true; ++ ++ hwc = gc->hwc.driver_data; ++ if (hwc && hwc->hwc_timeout == 0) ++ return false; ++ ++ return true; ++} ++ + static int mana_gd_query_max_resources(struct pci_dev *pdev) + { + struct gdma_context *gc = pci_get_drvdata(pdev); +@@ -278,8 +297,9 @@ static int mana_gd_disable_queue(struct gdma_queue *queue) + + err = mana_gd_send_request(gc, sizeof(req), &req, sizeof(resp), &resp); + if (err || resp.hdr.status) { +- dev_err(gc->dev, "Failed to disable queue: %d, 0x%x\n", err, +- resp.hdr.status); ++ if (mana_need_log(gc, err)) ++ dev_err(gc->dev, "Failed to disable queue: %d, 0x%x\n", err, ++ resp.hdr.status); + return err ? err : -EPROTO; + } + +@@ -366,25 +386,12 @@ EXPORT_SYMBOL_NS(mana_gd_ring_cq, "NET_MANA"); + + #define MANA_SERVICE_PERIOD 10 + +-struct mana_serv_work { +- struct work_struct serv_work; +- struct pci_dev *pdev; +-}; +- +-static void mana_serv_func(struct work_struct *w) ++static void mana_serv_fpga(struct pci_dev *pdev) + { +- struct mana_serv_work *mns_wk; + struct pci_bus *bus, *parent; +- struct pci_dev *pdev; +- +- mns_wk = container_of(w, struct mana_serv_work, serv_work); +- pdev = mns_wk->pdev; + + pci_lock_rescan_remove(); + +- if (!pdev) +- goto out; +- + bus = pdev->bus; + if (!bus) { + dev_err(&pdev->dev, "MANA service: no bus\n"); +@@ -405,7 +412,74 @@ static void mana_serv_func(struct work_struct *w) + + out: + pci_unlock_rescan_remove(); ++} ++ ++static void mana_serv_reset(struct pci_dev *pdev) ++{ ++ struct gdma_context *gc = pci_get_drvdata(pdev); ++ struct hw_channel_context *hwc; ++ ++ if (!gc) { ++ dev_err(&pdev->dev, "MANA service: no GC\n"); ++ return; ++ } ++ ++ hwc = gc->hwc.driver_data; ++ if (!hwc) { ++ dev_err(&pdev->dev, "MANA service: no HWC\n"); ++ goto out; ++ } ++ ++ /* HWC is not responding in this case, so don't wait */ ++ hwc->hwc_timeout = 0; ++ ++ dev_info(&pdev->dev, "MANA reset cycle start\n"); + ++ mana_gd_suspend(pdev, PMSG_SUSPEND); ++ ++ msleep(MANA_SERVICE_PERIOD * 1000); ++ ++ mana_gd_resume(pdev); ++ ++ dev_info(&pdev->dev, "MANA reset cycle completed\n"); ++ ++out: ++ gc->in_service = false; ++} ++ ++struct mana_serv_work { ++ struct work_struct serv_work; ++ struct pci_dev *pdev; ++ enum gdma_eqe_type type; ++}; ++ ++static void mana_serv_func(struct work_struct *w) ++{ ++ struct mana_serv_work *mns_wk; ++ struct pci_dev *pdev; ++ ++ mns_wk = container_of(w, struct mana_serv_work, serv_work); ++ pdev = mns_wk->pdev; ++ ++ if (!pdev) ++ goto out; ++ ++ switch (mns_wk->type) { ++ case GDMA_EQE_HWC_FPGA_RECONFIG: ++ mana_serv_fpga(pdev); ++ break; ++ ++ case GDMA_EQE_HWC_RESET_REQUEST: ++ mana_serv_reset(pdev); ++ break; ++ ++ default: ++ dev_err(&pdev->dev, "MANA service: unknown type %d\n", ++ mns_wk->type); ++ break; ++ } ++ ++out: + pci_dev_put(pdev); + kfree(mns_wk); + module_put(THIS_MODULE); +@@ -462,6 +536,7 @@ static void mana_gd_process_eqe(struct gdma_queue *eq) + break; + + case GDMA_EQE_HWC_FPGA_RECONFIG: ++ case GDMA_EQE_HWC_RESET_REQUEST: + dev_info(gc->dev, "Recv MANA service type:%d\n", type); + + if (gc->in_service) { +@@ -483,6 +558,7 @@ static void mana_gd_process_eqe(struct gdma_queue *eq) + dev_info(gc->dev, "Start MANA service type:%d\n", type); + gc->in_service = true; + mns_wk->pdev = to_pci_dev(gc->dev); ++ mns_wk->type = type; + pci_dev_get(mns_wk->pdev); + INIT_WORK(&mns_wk->serv_work, mana_serv_func); + schedule_work(&mns_wk->serv_work); +@@ -634,7 +710,8 @@ int mana_gd_test_eq(struct gdma_context *gc, struct gdma_queue *eq) + + err = mana_gd_send_request(gc, sizeof(req), &req, sizeof(resp), &resp); + if (err) { +- dev_err(dev, "test_eq failed: %d\n", err); ++ if (mana_need_log(gc, err)) ++ dev_err(dev, "test_eq failed: %d\n", err); + goto out; + } + +@@ -669,7 +746,7 @@ static void mana_gd_destroy_eq(struct gdma_context *gc, bool flush_evenets, + + if (flush_evenets) { + err = mana_gd_test_eq(gc, queue); +- if (err) ++ if (err && mana_need_log(gc, err)) + dev_warn(gc->dev, "Failed to flush EQ: %d\n", err); + } + +@@ -815,8 +892,9 @@ int mana_gd_destroy_dma_region(struct gdma_context *gc, u64 dma_region_handle) + + err = mana_gd_send_request(gc, sizeof(req), &req, sizeof(resp), &resp); + if (err || resp.hdr.status) { +- dev_err(gc->dev, "Failed to destroy DMA region: %d, 0x%x\n", +- err, resp.hdr.status); ++ if (mana_need_log(gc, err)) ++ dev_err(gc->dev, "Failed to destroy DMA region: %d, 0x%x\n", ++ err, resp.hdr.status); + return -EPROTO; + } + +@@ -1116,8 +1194,9 @@ int mana_gd_deregister_device(struct gdma_dev *gd) + + err = mana_gd_send_request(gc, sizeof(req), &req, sizeof(resp), &resp); + if (err || resp.hdr.status) { +- dev_err(gc->dev, "Failed to deregister device: %d, 0x%x\n", +- err, resp.hdr.status); ++ if (mana_need_log(gc, err)) ++ dev_err(gc->dev, "Failed to deregister device: %d, 0x%x\n", ++ err, resp.hdr.status); + if (!err) + err = -EPROTO; + } +@@ -1915,7 +1994,7 @@ static void mana_gd_remove(struct pci_dev *pdev) + } + + /* The 'state' parameter is not used. */ +-static int mana_gd_suspend(struct pci_dev *pdev, pm_message_t state) ++int mana_gd_suspend(struct pci_dev *pdev, pm_message_t state) + { + struct gdma_context *gc = pci_get_drvdata(pdev); + +@@ -1931,7 +2010,7 @@ static int mana_gd_suspend(struct pci_dev *pdev, pm_message_t state) + * fail -- if this happens, it's safer to just report an error than try to undo + * what has been done. + */ +-static int mana_gd_resume(struct pci_dev *pdev) ++int mana_gd_resume(struct pci_dev *pdev) + { + struct gdma_context *gc = pci_get_drvdata(pdev); + int err; +diff --git a/drivers/net/ethernet/microsoft/mana/hw_channel.c b/drivers/net/ethernet/microsoft/mana/hw_channel.c +index 650d22654d499..ef072e24c46d0 100644 +--- a/drivers/net/ethernet/microsoft/mana/hw_channel.c ++++ b/drivers/net/ethernet/microsoft/mana/hw_channel.c +@@ -880,7 +880,9 @@ int mana_hwc_send_request(struct hw_channel_context *hwc, u32 req_len, + + if (!wait_for_completion_timeout(&ctx->comp_event, + (msecs_to_jiffies(hwc->hwc_timeout)))) { +- dev_err(hwc->dev, "HWC: Request timed out!\n"); ++ if (hwc->hwc_timeout != 0) ++ dev_err(hwc->dev, "HWC: Request timed out!\n"); ++ + err = -ETIMEDOUT; + goto out; + } +diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c +index 016fd808ccad4..a7973651ae51b 100644 +--- a/drivers/net/ethernet/microsoft/mana/mana_en.c ++++ b/drivers/net/ethernet/microsoft/mana/mana_en.c +@@ -47,6 +47,15 @@ static const struct file_operations mana_dbg_q_fops = { + .read = mana_dbg_q_read, + }; + ++static bool mana_en_need_log(struct mana_port_context *apc, int err) ++{ ++ if (apc && apc->ac && apc->ac->gdma_dev && ++ apc->ac->gdma_dev->gdma_context) ++ return mana_need_log(apc->ac->gdma_dev->gdma_context, err); ++ else ++ return true; ++} ++ + /* Microsoft Azure Network Adapter (MANA) functions */ + + static int mana_open(struct net_device *ndev) +@@ -854,7 +863,8 @@ static int mana_send_request(struct mana_context *ac, void *in_buf, + if (err == -EOPNOTSUPP) + return err; + +- if (req->req.msg_type != MANA_QUERY_PHY_STAT) ++ if (req->req.msg_type != MANA_QUERY_PHY_STAT && ++ mana_need_log(gc, err)) + dev_err(dev, "Failed to send mana message: %d, 0x%x\n", + err, resp->status); + return err ? err : -EPROTO; +@@ -931,8 +941,10 @@ static void mana_pf_deregister_hw_vport(struct mana_port_context *apc) + err = mana_send_request(apc->ac, &req, sizeof(req), &resp, + sizeof(resp)); + if (err) { +- netdev_err(apc->ndev, "Failed to unregister hw vPort: %d\n", +- err); ++ if (mana_en_need_log(apc, err)) ++ netdev_err(apc->ndev, "Failed to unregister hw vPort: %d\n", ++ err); ++ + return; + } + +@@ -987,8 +999,10 @@ static void mana_pf_deregister_filter(struct mana_port_context *apc) + err = mana_send_request(apc->ac, &req, sizeof(req), &resp, + sizeof(resp)); + if (err) { +- netdev_err(apc->ndev, "Failed to unregister filter: %d\n", +- err); ++ if (mana_en_need_log(apc, err)) ++ netdev_err(apc->ndev, "Failed to unregister filter: %d\n", ++ err); ++ + return; + } + +@@ -1218,7 +1232,9 @@ static int mana_cfg_vport_steering(struct mana_port_context *apc, + err = mana_send_request(apc->ac, req, req_buf_size, &resp, + sizeof(resp)); + if (err) { +- netdev_err(ndev, "Failed to configure vPort RX: %d\n", err); ++ if (mana_en_need_log(apc, err)) ++ netdev_err(ndev, "Failed to configure vPort RX: %d\n", err); ++ + goto out; + } + +@@ -1402,7 +1418,9 @@ void mana_destroy_wq_obj(struct mana_port_context *apc, u32 wq_type, + err = mana_send_request(apc->ac, &req, sizeof(req), &resp, + sizeof(resp)); + if (err) { +- netdev_err(ndev, "Failed to destroy WQ object: %d\n", err); ++ if (mana_en_need_log(apc, err)) ++ netdev_err(ndev, "Failed to destroy WQ object: %d\n", err); ++ + return; + } + +@@ -3067,11 +3085,10 @@ static int mana_dealloc_queues(struct net_device *ndev) + + apc->rss_state = TRI_STATE_FALSE; + err = mana_config_rss(apc, TRI_STATE_FALSE, false, false); +- if (err) { ++ if (err && mana_en_need_log(apc, err)) + netdev_err(ndev, "Failed to disable vPort: %d\n", err); +- return err; +- } + ++ /* Even in err case, still need to cleanup the vPort */ + mana_destroy_vport(apc); + + return 0; +diff --git a/include/net/mana/gdma.h b/include/net/mana/gdma.h +index 92ab85061df00..57df78cfbf82c 100644 +--- a/include/net/mana/gdma.h ++++ b/include/net/mana/gdma.h +@@ -62,6 +62,7 @@ enum gdma_eqe_type { + GDMA_EQE_HWC_FPGA_RECONFIG = 132, + GDMA_EQE_HWC_SOC_RECONFIG_DATA = 133, + GDMA_EQE_HWC_SOC_SERVICE = 134, ++ GDMA_EQE_HWC_RESET_REQUEST = 135, + GDMA_EQE_RNIC_QP_FATAL = 176, + }; + +@@ -584,6 +585,9 @@ enum { + /* Driver supports dynamic MSI-X vector allocation */ + #define GDMA_DRV_CAP_FLAG_1_DYNAMIC_IRQ_ALLOC_SUPPORT BIT(13) + ++/* Driver can self reset on EQE notification */ ++#define GDMA_DRV_CAP_FLAG_1_SELF_RESET_ON_EQE BIT(14) ++ + /* Driver can self reset on FPGA Reconfig EQE notification */ + #define GDMA_DRV_CAP_FLAG_1_HANDLE_RECONFIG_EQE BIT(17) + +@@ -594,6 +598,7 @@ enum { + GDMA_DRV_CAP_FLAG_1_VARIABLE_INDIRECTION_TABLE_SUPPORT | \ + GDMA_DRV_CAP_FLAG_1_DEV_LIST_HOLES_SUP | \ + GDMA_DRV_CAP_FLAG_1_DYNAMIC_IRQ_ALLOC_SUPPORT | \ ++ GDMA_DRV_CAP_FLAG_1_SELF_RESET_ON_EQE | \ + GDMA_DRV_CAP_FLAG_1_HANDLE_RECONFIG_EQE) + + #define GDMA_DRV_CAP_FLAGS2 0 +@@ -921,4 +926,9 @@ void mana_unregister_debugfs(void); + + int mana_rdma_service_event(struct gdma_context *gc, enum gdma_service_type event); + ++int mana_gd_suspend(struct pci_dev *pdev, pm_message_t state); ++int mana_gd_resume(struct pci_dev *pdev); ++ ++bool mana_need_log(struct gdma_context *gc, int err); ++ + #endif /* _GDMA_H */ +-- +2.51.2 + +EOF + +cat << 'EOF' > expected +diff -u b/drivers/net/ethernet/microsoft/mana/gdma_main.c b/drivers/net/ethernet/microsoft/mana/gdma_main.c +--- b/drivers/net/ethernet/microsoft/mana/gdma_main.c ++++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c +@@ -5,7 +5,7 @@ +-#include ++#include + + #include + #include + ++#include + struct dentry *mana_debugfs_root; +- +@@ -65,7 +65,7 @@ + mana_gd_init_vf_regs(pdev); + } + +-/* Suppress logging when we set timeout to zeo */ ++/* Suppress logging when we set timeout to zero */ + bool mana_need_log(struct gdma_context *gc, int err) + { + struct hw_channel_context *hwc; +diff -u b/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c +--- b/drivers/net/ethernet/microsoft/mana/mana_en.c ++++ b/drivers/net/ethernet/microsoft/mana/mana_en.c +@@ -777,7 +786,8 @@ INTERDIFF: rejected hunk from patch1, cannot diff context + err = mana_gd_send_request(gc, in_len, in_buf, out_len, + out_buf); + if (err || resp->status) { +- if (req->req.msg_type != MANA_QUERY_PHY_STAT) ++ if (req->req.msg_type != MANA_QUERY_PHY_STAT && ++ mana_need_log(gc, err)) + dev_err(dev, "Failed to send mana message: %d, 0x%x\n", + err, resp->status); + return err ? err : -EPROTO; +@@ -863,7 +872,8 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + if (err == -EOPNOTSUPP) + return err; + +- if (req->req.msg_type != MANA_QUERY_PHY_STAT) ++ if (req->req.msg_type != MANA_QUERY_PHY_STAT && ++ mana_need_log(gc, err)) + dev_err(dev, "Failed to send mana message: %d, 0x%x\n", + err, resp->status); + return err ? err : -EPROTO; +diff -u b/include/net/mana/gdma.h b/include/net/mana/gdma.h +--- b/include/net/mana/gdma.h ++++ b/include/net/mana/gdma.h +@@ -57,6 +57,6 @@ ++ GDMA_EQE_HWC_INIT_DONE = 131, + GDMA_EQE_HWC_FPGA_RECONFIG = 132, + GDMA_EQE_HWC_SOC_RECONFIG_DATA = 133, +- GDMA_EQE_HWC_SOC_SERVICE = 134, + GDMA_EQE_RNIC_QP_FATAL = 176, + }; + +@@ -60,6 +60,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context + GDMA_EQE_HWC_INIT_DONE = 131, + GDMA_EQE_HWC_FPGA_RECONFIG = 132, + GDMA_EQE_HWC_SOC_RECONFIG_DATA = 133, ++ GDMA_EQE_HWC_RESET_REQUEST = 135, + GDMA_EQE_RNIC_QP_FATAL = 176, + }; + +@@ -62,6 +62,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + GDMA_EQE_HWC_FPGA_RECONFIG = 132, + GDMA_EQE_HWC_SOC_RECONFIG_DATA = 133, + GDMA_EQE_HWC_SOC_SERVICE = 134, ++ GDMA_EQE_HWC_RESET_REQUEST = 135, + GDMA_EQE_RNIC_QP_FATAL = 176, + }; + +@@ -556,5 +554,5 @@ +-/* Driver supports dynamic MSI-X vector allocation */ +-#define GDMA_DRV_CAP_FLAG_1_DYNAMIC_IRQ_ALLOC_SUPPORT BIT(13) ++/* Driver can handle holes (zeros) in the device list */ ++#define GDMA_DRV_CAP_FLAG_1_DEV_LIST_HOLES_SUP BIT(11) + + /* Driver can self reset on EQE notification */ + #define GDMA_DRV_CAP_FLAG_1_SELF_RESET_ON_EQE BIT(14) +@@ -565,9 +565,9 @@ + /* Driver can self reset on FPGA Reconfig EQE notification */ + #define GDMA_DRV_CAP_FLAG_1_HANDLE_RECONFIG_EQE BIT(17) + ++ GDMA_DRV_CAP_FLAG_1_HWC_TIMEOUT_RECONFIG | \ + GDMA_DRV_CAP_FLAG_1_VARIABLE_INDIRECTION_TABLE_SUPPORT | \ + GDMA_DRV_CAP_FLAG_1_DEV_LIST_HOLES_SUP | \ +- GDMA_DRV_CAP_FLAG_1_DYNAMIC_IRQ_ALLOC_SUPPORT | \ + GDMA_DRV_CAP_FLAG_1_HANDLE_RECONFIG_EQE) + + #define GDMA_DRV_CAP_FLAGS2 0 +@@ -571,6 +575,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context + GDMA_DRV_CAP_FLAG_1_HWC_TIMEOUT_RECONFIG | \ + GDMA_DRV_CAP_FLAG_1_VARIABLE_INDIRECTION_TABLE_SUPPORT | \ + GDMA_DRV_CAP_FLAG_1_DEV_LIST_HOLES_SUP | \ ++ GDMA_DRV_CAP_FLAG_1_SELF_RESET_ON_EQE | \ + GDMA_DRV_CAP_FLAG_1_HANDLE_RECONFIG_EQE) + + #define GDMA_DRV_CAP_FLAGS2 0 +@@ -597,6 +601,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + GDMA_DRV_CAP_FLAG_1_VARIABLE_INDIRECTION_TABLE_SUPPORT | \ + GDMA_DRV_CAP_FLAG_1_DEV_LIST_HOLES_SUP | \ + GDMA_DRV_CAP_FLAG_1_DYNAMIC_IRQ_ALLOC_SUPPORT | \ ++ GDMA_DRV_CAP_FLAG_1_SELF_RESET_ON_EQE | \ + GDMA_DRV_CAP_FLAG_1_HANDLE_RECONFIG_EQE) + + #define GDMA_DRV_CAP_FLAGS2 0 +@@ -892,5 +892,5 @@ +- +-int mana_rdma_service_event(struct gdma_context *gc, enum gdma_service_type event); ++void mana_register_debugfs(void); ++void mana_unregister_debugfs(void); + + int mana_gd_suspend(struct pci_dev *pdev, pm_message_t state); + int mana_gd_resume(struct pci_dev *pdev); +EOF + +${INTERDIFF} --fuzzy patch1 patch2 2>errors >output +[ -s errors ] && exit 1 + +cmp output expected || exit 1 diff --git a/tests/fuzzy8/run-test b/tests/fuzzy8/run-test new file mode 100755 index 0000000..680f3fa --- /dev/null +++ b/tests/fuzzy8/run-test @@ -0,0 +1,1760 @@ +#!/bin/sh + +# This is an interdiff(1) testcase. +# Test: Fuzzy diffing (using --fuzzy=3) with a real Linux kernel backport +# compared against its upstream version. Stresses having multiple relocations +# and most of the fuzzy diffing machinery as a whole. + + +. ${top_srcdir-.}/tests/common.sh + +cat << 'EOF' > patch1 +From b0c8e943e409740752a9a34c96743088094223e9 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Marcin=20Wcis=C5=82o?= +Date: Tue, 4 Nov 2025 20:51:12 +0100 +Subject: [PATCH] netfilter: nf_tables: report use refcount overflow +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +jira VULN-430 +cve-pre CVE-2023-4244 +commit-author Pablo Neira Ayuso +commit 1689f25924ada8fe14a4a82c38925d04994c7142 +upstream-diff Used the cleanly applying 9.4 backport + 854ec8345abb60f1fb65446a6aef2627f71196ca + +Overflow use refcount checks are not complete. + +Add helper function to deal with object reference counter tracking. +Report -EMFILE in case UINT_MAX is reached. + +nft_use_dec() splats in case that reference counter underflows, +which should not ever happen. + +Add nft_use_inc_restore() and nft_use_dec_restore() which are used +to restore reference counter from error and abort paths. + +Use u32 in nft_flowtable and nft_object since helper functions cannot +work on bitfields. + +Remove the few early incomplete checks now that the helper functions +are in place and used to check for refcount overflow. + +Fixes: 96518518cc41 ("netfilter: add nftables") + Signed-off-by: Pablo Neira Ayuso +(cherry picked from commit 1689f25924ada8fe14a4a82c38925d04994c7142) + Signed-off-by: Marcin Wcisło +--- + include/net/netfilter/nf_tables.h | 31 +++++- + net/netfilter/nf_tables_api.c | 163 ++++++++++++++++++------------ + net/netfilter/nft_flow_offload.c | 6 +- + net/netfilter/nft_immediate.c | 8 +- + net/netfilter/nft_objref.c | 8 +- + 5 files changed, 141 insertions(+), 75 deletions(-) + +diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h +index ccb3b3e4ce88e..3554c8ea03d3e 100644 +--- a/include/net/netfilter/nf_tables.h ++++ b/include/net/netfilter/nf_tables.h +@@ -1145,6 +1145,29 @@ int __nft_release_basechain(struct nft_ctx *ctx); + + unsigned int nft_do_chain(struct nft_pktinfo *pkt, void *priv); + ++static inline bool nft_use_inc(u32 *use) ++{ ++ if (*use == UINT_MAX) ++ return false; ++ ++ (*use)++; ++ ++ return true; ++} ++ ++static inline void nft_use_dec(u32 *use) ++{ ++ WARN_ON_ONCE((*use)-- == 0); ++} ++ ++/* For error and abort path: restore use counter to previous state. */ ++static inline void nft_use_inc_restore(u32 *use) ++{ ++ WARN_ON_ONCE(!nft_use_inc(use)); ++} ++ ++#define nft_use_dec_restore nft_use_dec ++ + /** + * struct nft_table - nf_tables table + * +@@ -1228,8 +1251,8 @@ struct nft_object { + struct list_head list; + struct rhlist_head rhlhead; + struct nft_object_hash_key key; +- u32 genmask:2, +- use:30; ++ u32 genmask:2; ++ u32 use; + u64 handle; + u16 udlen; + u8 *udata; +@@ -1331,8 +1354,8 @@ struct nft_flowtable { + char *name; + int hooknum; + int ops_len; +- u32 genmask:2, +- use:30; ++ u32 genmask:2; ++ u32 use; + u64 handle; + /* runtime data below here */ + struct list_head hook_list ____cacheline_aligned; +diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c +index 943bcc9342ea6..56291ca0d6518 100644 +--- a/net/netfilter/nf_tables_api.c ++++ b/net/netfilter/nf_tables_api.c +@@ -255,8 +255,10 @@ int nf_tables_bind_chain(const struct nft_ctx *ctx, struct nft_chain *chain) + if (chain->bound) + return -EBUSY; + ++ if (!nft_use_inc(&chain->use)) ++ return -EMFILE; ++ + chain->bound = true; +- chain->use++; + nft_chain_trans_bind(ctx, chain); + + return 0; +@@ -439,7 +441,7 @@ static int nft_delchain(struct nft_ctx *ctx) + if (IS_ERR(trans)) + return PTR_ERR(trans); + +- ctx->table->use--; ++ nft_use_dec(&ctx->table->use); + nft_deactivate_next(ctx->net, ctx->chain); + + return 0; +@@ -478,7 +480,7 @@ nf_tables_delrule_deactivate(struct nft_ctx *ctx, struct nft_rule *rule) + /* You cannot delete the same rule twice */ + if (nft_is_active_next(ctx->net, rule)) { + nft_deactivate_next(ctx->net, rule); +- ctx->chain->use--; ++ nft_use_dec(&ctx->chain->use); + return 0; + } + return -ENOENT; +@@ -645,7 +647,7 @@ static int nft_delset(const struct nft_ctx *ctx, struct nft_set *set) + nft_map_deactivate(ctx, set); + + nft_deactivate_next(ctx->net, set); +- ctx->table->use--; ++ nft_use_dec(&ctx->table->use); + + return err; + } +@@ -677,7 +679,7 @@ static int nft_delobj(struct nft_ctx *ctx, struct nft_object *obj) + return err; + + nft_deactivate_next(ctx->net, obj); +- ctx->table->use--; ++ nft_use_dec(&ctx->table->use); + + return err; + } +@@ -712,7 +714,7 @@ static int nft_delflowtable(struct nft_ctx *ctx, + return err; + + nft_deactivate_next(ctx->net, flowtable); +- ctx->table->use--; ++ nft_use_dec(&ctx->table->use); + + return err; + } +@@ -2298,9 +2300,6 @@ static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask, + struct nft_rule **rules; + int err; + +- if (table->use == UINT_MAX) +- return -EOVERFLOW; +- + if (nla[NFTA_CHAIN_HOOK]) { + struct nft_stats __percpu *stats = NULL; + struct nft_chain_hook hook; +@@ -2397,6 +2396,11 @@ static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask, + if (err < 0) + goto err_destroy_chain; + ++ if (!nft_use_inc(&table->use)) { ++ err = -EMFILE; ++ goto err_use; ++ } ++ + trans = nft_trans_chain_add(ctx, NFT_MSG_NEWCHAIN); + if (IS_ERR(trans)) { + err = PTR_ERR(trans); +@@ -2413,10 +2417,11 @@ static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask, + goto err_unregister_hook; + } + +- table->use++; +- + return 0; ++ + err_unregister_hook: ++ nft_use_dec_restore(&table->use); ++err_use: + nf_tables_unregister_hook(net, table, chain); + err_destroy_chain: + nf_tables_chain_destroy(ctx); +@@ -3616,9 +3621,6 @@ static int nf_tables_newrule(struct sk_buff *skb, const struct nfnl_info *info, + return -EINVAL; + handle = nf_tables_alloc_handle(table); + +- if (chain->use == UINT_MAX) +- return -EOVERFLOW; +- + if (nla[NFTA_RULE_POSITION]) { + pos_handle = be64_to_cpu(nla_get_be64(nla[NFTA_RULE_POSITION])); + old_rule = __nft_rule_lookup(chain, pos_handle); +@@ -3712,6 +3714,11 @@ static int nf_tables_newrule(struct sk_buff *skb, const struct nfnl_info *info, + } + } + ++ if (!nft_use_inc(&chain->use)) { ++ err = -EMFILE; ++ goto err_release_rule; ++ } ++ + if (info->nlh->nlmsg_flags & NLM_F_REPLACE) { + err = nft_delrule(&ctx, old_rule); + if (err < 0) +@@ -3743,7 +3750,6 @@ static int nf_tables_newrule(struct sk_buff *skb, const struct nfnl_info *info, + } + } + kvfree(expr_info); +- chain->use++; + + if (flow) + nft_trans_flow_rule(trans) = flow; +@@ -3754,6 +3760,7 @@ static int nf_tables_newrule(struct sk_buff *skb, const struct nfnl_info *info, + return 0; + + err_destroy_flow_rule: ++ nft_use_dec_restore(&chain->use); + if (flow) + nft_flow_rule_destroy(flow); + err_release_rule: +@@ -4786,9 +4793,15 @@ static int nf_tables_newset(struct sk_buff *skb, const struct nfnl_info *info, + alloc_size = sizeof(*set) + size + udlen; + if (alloc_size < size || alloc_size > INT_MAX) + return -ENOMEM; ++ ++ if (!nft_use_inc(&table->use)) ++ return -EMFILE; ++ + set = kvzalloc(alloc_size, GFP_KERNEL); +- if (!set) +- return -ENOMEM; ++ if (!set) { ++ err = -ENOMEM; ++ goto err_alloc; ++ } + + name = nla_strdup(nla[NFTA_SET_NAME], GFP_KERNEL); + if (!name) { +@@ -4846,7 +4859,7 @@ static int nf_tables_newset(struct sk_buff *skb, const struct nfnl_info *info, + goto err_set_expr_alloc; + + list_add_tail_rcu(&set->list, &table->sets); +- table->use++; ++ + return 0; + + err_set_expr_alloc: +@@ -4858,6 +4871,9 @@ err_set_init: + kfree(set->name); + err_set_name: + kvfree(set); ++err_alloc: ++ nft_use_dec_restore(&table->use); ++ + return err; + } + +@@ -4996,9 +5012,6 @@ int nf_tables_bind_set(const struct nft_ctx *ctx, struct nft_set *set, + struct nft_set_binding *i; + struct nft_set_iter iter; + +- if (set->use == UINT_MAX) +- return -EOVERFLOW; +- + if (!list_empty(&set->bindings) && nft_set_is_anonymous(set)) + return -EBUSY; + +@@ -5026,10 +5039,12 @@ int nf_tables_bind_set(const struct nft_ctx *ctx, struct nft_set *set, + return iter.err; + } + bind: ++ if (!nft_use_inc(&set->use)) ++ return -EMFILE; ++ + binding->chain = ctx->chain; + list_add_tail_rcu(&binding->list, &set->bindings); + nft_set_trans_bind(ctx, set); +- set->use++; + + return 0; + } +@@ -5103,7 +5118,7 @@ void nf_tables_activate_set(const struct nft_ctx *ctx, struct nft_set *set) + nft_clear(ctx->net, set); + } + +- set->use++; ++ nft_use_inc_restore(&set->use); + } + EXPORT_SYMBOL_GPL(nf_tables_activate_set); + +@@ -5119,7 +5134,7 @@ void nf_tables_deactivate_set(const struct nft_ctx *ctx, struct nft_set *set, + else + list_del_rcu(&binding->list); + +- set->use--; ++ nft_use_dec(&set->use); + break; + case NFT_TRANS_PREPARE: + if (nft_set_is_anonymous(set)) { +@@ -5128,7 +5143,7 @@ void nf_tables_deactivate_set(const struct nft_ctx *ctx, struct nft_set *set, + + nft_deactivate_next(ctx->net, set); + } +- set->use--; ++ nft_use_dec(&set->use); + return; + case NFT_TRANS_ABORT: + case NFT_TRANS_RELEASE: +@@ -5136,7 +5151,7 @@ void nf_tables_deactivate_set(const struct nft_ctx *ctx, struct nft_set *set, + set->flags & (NFT_SET_MAP | NFT_SET_OBJECT)) + nft_map_deactivate(ctx, set); + +- set->use--; ++ nft_use_dec(&set->use); + fallthrough; + default: + nf_tables_unbind_set(ctx, set, binding, +@@ -5927,7 +5942,7 @@ void nft_set_elem_destroy(const struct nft_set *set, void *elem, + nft_set_elem_expr_destroy(&ctx, nft_set_ext_expr(ext)); + + if (nft_set_ext_exists(ext, NFT_SET_EXT_OBJREF)) +- (*nft_set_ext_obj(ext))->use--; ++ nft_use_dec(&(*nft_set_ext_obj(ext))->use); + kfree(elem); + } + EXPORT_SYMBOL_GPL(nft_set_elem_destroy); +@@ -6429,8 +6444,16 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, + set->objtype, genmask); + if (IS_ERR(obj)) { + err = PTR_ERR(obj); ++ obj = NULL; + goto err_parse_key_end; + } ++ ++ if (!nft_use_inc(&obj->use)) { ++ err = -EMFILE; ++ obj = NULL; ++ goto err_parse_key_end; ++ } ++ + err = nft_set_ext_add(&tmpl, NFT_SET_EXT_OBJREF); + if (err < 0) + goto err_parse_key_end; +@@ -6499,10 +6522,9 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, + if (flags) + *nft_set_ext_flags(ext) = flags; + +- if (obj) { ++ if (obj) + *nft_set_ext_obj(ext) = obj; +- obj->use++; +- } ++ + if (ulen > 0) { + if (nft_set_ext_check(&tmpl, NFT_SET_EXT_USERDATA, ulen) < 0) { + err = -EINVAL; +@@ -6567,12 +6589,13 @@ err_element_clash: + kfree(trans); + err_elem_free: + nf_tables_set_elem_destroy(ctx, set, elem.priv); +- if (obj) +- obj->use--; + err_parse_data: + if (nla[NFTA_SET_ELEM_DATA] != NULL) + nft_data_release(&elem.data.val, desc.type); + err_parse_key_end: ++ if (obj) ++ nft_use_dec_restore(&obj->use); ++ + nft_data_release(&elem.key_end.val, NFT_DATA_VALUE); + err_parse_key: + nft_data_release(&elem.key.val, NFT_DATA_VALUE); +@@ -6653,7 +6676,7 @@ void nft_data_hold(const struct nft_data *data, enum nft_data_types type) + case NFT_JUMP: + case NFT_GOTO: + chain = data->verdict.chain; +- chain->use++; ++ nft_use_inc_restore(&chain->use); + break; + } + } +@@ -6668,7 +6691,7 @@ static void nft_setelem_data_activate(const struct net *net, + if (nft_set_ext_exists(ext, NFT_SET_EXT_DATA)) + nft_data_hold(nft_set_ext_data(ext), set->dtype); + if (nft_set_ext_exists(ext, NFT_SET_EXT_OBJREF)) +- (*nft_set_ext_obj(ext))->use++; ++ nft_use_inc_restore(&(*nft_set_ext_obj(ext))->use); + } + + static void nft_setelem_data_deactivate(const struct net *net, +@@ -6680,7 +6703,7 @@ static void nft_setelem_data_deactivate(const struct net *net, + if (nft_set_ext_exists(ext, NFT_SET_EXT_DATA)) + nft_data_release(nft_set_ext_data(ext), set->dtype); + if (nft_set_ext_exists(ext, NFT_SET_EXT_OBJREF)) +- (*nft_set_ext_obj(ext))->use--; ++ nft_use_dec(&(*nft_set_ext_obj(ext))->use); + } + + static int nft_del_setelem(struct nft_ctx *ctx, struct nft_set *set, +@@ -7220,9 +7243,14 @@ static int nf_tables_newobj(struct sk_buff *skb, const struct nfnl_info *info, + + nft_ctx_init(&ctx, net, skb, info->nlh, family, table, NULL, nla); + ++ if (!nft_use_inc(&table->use)) ++ return -EMFILE; ++ + type = nft_obj_type_get(net, objtype); +- if (IS_ERR(type)) +- return PTR_ERR(type); ++ if (IS_ERR(type)) { ++ err = PTR_ERR(type); ++ goto err_type; ++ } + + obj = nft_obj_init(&ctx, type, nla[NFTA_OBJ_DATA]); + if (IS_ERR(obj)) { +@@ -7256,7 +7284,7 @@ static int nf_tables_newobj(struct sk_buff *skb, const struct nfnl_info *info, + goto err_obj_ht; + + list_add_tail_rcu(&obj->list, &table->objects); +- table->use++; ++ + return 0; + err_obj_ht: + /* queued in transaction log */ +@@ -7272,6 +7300,9 @@ err_strdup: + kfree(obj); + err_init: + module_put(type->owner); ++err_type: ++ nft_use_dec_restore(&table->use); ++ + return err; + } + +@@ -7666,7 +7697,7 @@ void nf_tables_deactivate_flowtable(const struct nft_ctx *ctx, + case NFT_TRANS_PREPARE: + case NFT_TRANS_ABORT: + case NFT_TRANS_RELEASE: +- flowtable->use--; ++ nft_use_dec(&flowtable->use); + fallthrough; + default: + return; +@@ -8014,9 +8045,14 @@ static int nf_tables_newflowtable(struct sk_buff *skb, + + nft_ctx_init(&ctx, net, skb, info->nlh, family, table, NULL, nla); + ++ if (!nft_use_inc(&table->use)) ++ return -EMFILE; ++ + flowtable = kzalloc(sizeof(*flowtable), GFP_KERNEL); +- if (!flowtable) +- return -ENOMEM; ++ if (!flowtable) { ++ err = -ENOMEM; ++ goto flowtable_alloc; ++ } + + flowtable->table = table; + flowtable->handle = nf_tables_alloc_handle(table); +@@ -8071,7 +8107,6 @@ static int nf_tables_newflowtable(struct sk_buff *skb, + goto err5; + + list_add_tail_rcu(&flowtable->list, &table->flowtables); +- table->use++; + + return 0; + err5: +@@ -8088,6 +8123,9 @@ err2: + kfree(flowtable->name); + err1: + kfree(flowtable); ++flowtable_alloc: ++ nft_use_dec_restore(&table->use); ++ + return err; + } + +@@ -9392,7 +9430,7 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb) + */ + if (nft_set_is_anonymous(nft_trans_set(trans)) && + !list_empty(&nft_trans_set(trans)->bindings)) +- trans->ctx.table->use--; ++ nft_use_dec(&trans->ctx.table->use); + } + nf_tables_set_notify(&trans->ctx, nft_trans_set(trans), + NFT_MSG_NEWSET, GFP_KERNEL); +@@ -9616,7 +9654,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + nft_trans_destroy(trans); + break; + } +- trans->ctx.table->use--; ++ nft_use_dec_restore(&trans->ctx.table->use); + nft_chain_del(trans->ctx.chain); + nf_tables_unregister_hook(trans->ctx.net, + trans->ctx.table, +@@ -9625,7 +9663,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + break; + case NFT_MSG_DELCHAIN: + case NFT_MSG_DESTROYCHAIN: +- trans->ctx.table->use++; ++ nft_use_inc_restore(&trans->ctx.table->use); + nft_clear(trans->ctx.net, trans->ctx.chain); + nft_trans_destroy(trans); + break; +@@ -9634,7 +9672,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + nft_trans_destroy(trans); + break; + } +- trans->ctx.chain->use--; ++ nft_use_dec_restore(&trans->ctx.chain->use); + list_del_rcu(&nft_trans_rule(trans)->list); + nft_rule_expr_deactivate(&trans->ctx, + nft_trans_rule(trans), +@@ -9644,7 +9682,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + break; + case NFT_MSG_DELRULE: + case NFT_MSG_DESTROYRULE: +- trans->ctx.chain->use++; ++ nft_use_inc_restore(&trans->ctx.chain->use); + nft_clear(trans->ctx.net, nft_trans_rule(trans)); + nft_rule_expr_activate(&trans->ctx, nft_trans_rule(trans)); + if (trans->ctx.chain->flags & NFT_CHAIN_HW_OFFLOAD) +@@ -9657,7 +9695,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + nft_trans_destroy(trans); + break; + } +- trans->ctx.table->use--; ++ nft_use_dec_restore(&trans->ctx.table->use); + if (nft_trans_set_bound(trans)) { + nft_trans_destroy(trans); + break; +@@ -9666,7 +9704,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + break; + case NFT_MSG_DELSET: + case NFT_MSG_DESTROYSET: +- trans->ctx.table->use++; ++ nft_use_inc_restore(&trans->ctx.table->use); + nft_clear(trans->ctx.net, nft_trans_set(trans)); + if (nft_trans_set(trans)->flags & (NFT_SET_MAP | NFT_SET_OBJECT)) + nft_map_activate(&trans->ctx, nft_trans_set(trans)); +@@ -9710,13 +9748,13 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + nft_obj_destroy(&trans->ctx, nft_trans_obj_newobj(trans)); + nft_trans_destroy(trans); + } else { +- trans->ctx.table->use--; ++ nft_use_dec_restore(&trans->ctx.table->use); + nft_obj_del(nft_trans_obj(trans)); + } + break; + case NFT_MSG_DELOBJ: + case NFT_MSG_DESTROYOBJ: +- trans->ctx.table->use++; ++ nft_use_inc_restore(&trans->ctx.table->use); + nft_clear(trans->ctx.net, nft_trans_obj(trans)); + nft_trans_destroy(trans); + break; +@@ -9725,7 +9763,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + nft_unregister_flowtable_net_hooks(net, + &nft_trans_flowtable_hooks(trans)); + } else { +- trans->ctx.table->use--; ++ nft_use_dec_restore(&trans->ctx.table->use); + list_del_rcu(&nft_trans_flowtable(trans)->list); + nft_unregister_flowtable_net_hooks(net, + &nft_trans_flowtable(trans)->hook_list); +@@ -9737,7 +9775,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + list_splice(&nft_trans_flowtable_hooks(trans), + &nft_trans_flowtable(trans)->hook_list); + } else { +- trans->ctx.table->use++; ++ nft_use_inc_restore(&trans->ctx.table->use); + nft_clear(trans->ctx.net, nft_trans_flowtable(trans)); + } + nft_trans_destroy(trans); +@@ -10181,8 +10219,9 @@ static int nft_verdict_init(const struct nft_ctx *ctx, struct nft_data *data, + if (desc->flags & NFT_DATA_DESC_SETELEM && + chain->flags & NFT_CHAIN_BINDING) + return -EINVAL; ++ if (!nft_use_inc(&chain->use)) ++ return -EMFILE; + +- chain->use++; + data->verdict.chain = chain; + break; + default: +@@ -10202,7 +10241,7 @@ static void nft_verdict_uninit(const struct nft_data *data) + case NFT_JUMP: + case NFT_GOTO: + chain = data->verdict.chain; +- chain->use--; ++ nft_use_dec(&chain->use); + break; + } + } +@@ -10371,11 +10410,11 @@ int __nft_release_basechain(struct nft_ctx *ctx) + nf_tables_unregister_hook(ctx->net, ctx->chain->table, ctx->chain); + list_for_each_entry_safe(rule, nr, &ctx->chain->rules, list) { + list_del(&rule->list); +- ctx->chain->use--; ++ nft_use_dec(&ctx->chain->use); + nf_tables_rule_release(ctx, rule); + } + nft_chain_del(ctx->chain); +- ctx->table->use--; ++ nft_use_dec(&ctx->table->use); + nf_tables_chain_destroy(ctx); + + return 0; +@@ -10425,18 +10464,18 @@ static void __nft_release_table(struct net *net, struct nft_table *table) + ctx.chain = chain; + list_for_each_entry_safe(rule, nr, &chain->rules, list) { + list_del(&rule->list); +- chain->use--; ++ nft_use_dec(&chain->use); + nf_tables_rule_release(&ctx, rule); + } + } + list_for_each_entry_safe(flowtable, nf, &table->flowtables, list) { + list_del(&flowtable->list); +- table->use--; ++ nft_use_dec(&table->use); + nf_tables_flowtable_destroy(flowtable); + } + list_for_each_entry_safe(set, ns, &table->sets, list) { + list_del(&set->list); +- table->use--; ++ nft_use_dec(&table->use); + if (set->flags & (NFT_SET_MAP | NFT_SET_OBJECT)) + nft_map_deactivate(&ctx, set); + +@@ -10444,13 +10483,13 @@ static void __nft_release_table(struct net *net, struct nft_table *table) + } + list_for_each_entry_safe(obj, ne, &table->objects, list) { + nft_obj_del(obj); +- table->use--; ++ nft_use_dec(&table->use); + nft_obj_destroy(&ctx, obj); + } + list_for_each_entry_safe(chain, nc, &table->chains, list) { + ctx.chain = chain; + nft_chain_del(chain); +- table->use--; ++ nft_use_dec(&table->use); + nf_tables_chain_destroy(&ctx); + } + nf_tables_table_destroy(&ctx); +diff --git a/net/netfilter/nft_flow_offload.c b/net/netfilter/nft_flow_offload.c +index 6db8c802d5e76..9a05fca9c48b7 100644 +--- a/net/netfilter/nft_flow_offload.c ++++ b/net/netfilter/nft_flow_offload.c +@@ -381,8 +381,10 @@ static int nft_flow_offload_init(const struct nft_ctx *ctx, + if (IS_ERR(flowtable)) + return PTR_ERR(flowtable); + ++ if (!nft_use_inc(&flowtable->use)) ++ return -EMFILE; ++ + priv->flowtable = flowtable; +- flowtable->use++; + + return nf_ct_netns_get(ctx->net, ctx->family); + } +@@ -401,7 +403,7 @@ static void nft_flow_offload_activate(const struct nft_ctx *ctx, + { + struct nft_flow_offload *priv = nft_expr_priv(expr); + +- priv->flowtable->use++; ++ nft_use_inc_restore(&priv->flowtable->use); + } + + static void nft_flow_offload_destroy(const struct nft_ctx *ctx, +diff --git a/net/netfilter/nft_immediate.c b/net/netfilter/nft_immediate.c +index 7c810005a1f9f..11a39289fe49b 100644 +--- a/net/netfilter/nft_immediate.c ++++ b/net/netfilter/nft_immediate.c +@@ -159,7 +159,7 @@ static void nft_immediate_deactivate(const struct nft_ctx *ctx, + default: + nft_chain_del(chain); + chain->bound = false; +- chain->table->use--; ++ nft_use_dec(&chain->table->use); + break; + } + break; +@@ -198,7 +198,7 @@ static void nft_immediate_destroy(const struct nft_ctx *ctx, + * let the transaction records release this chain and its rules. + */ + if (chain->bound) { +- chain->use--; ++ nft_use_dec(&chain->use); + break; + } + +@@ -206,9 +206,9 @@ static void nft_immediate_destroy(const struct nft_ctx *ctx, + chain_ctx = *ctx; + chain_ctx.chain = chain; + +- chain->use--; ++ nft_use_dec(&chain->use); + list_for_each_entry_safe(rule, n, &chain->rules, list) { +- chain->use--; ++ nft_use_dec(&chain->use); + list_del(&rule->list); + nf_tables_rule_destroy(&chain_ctx, rule); + } +diff --git a/net/netfilter/nft_objref.c b/net/netfilter/nft_objref.c +index e873401182899..10850266221a6 100644 +--- a/net/netfilter/nft_objref.c ++++ b/net/netfilter/nft_objref.c +@@ -41,8 +41,10 @@ static int nft_objref_init(const struct nft_ctx *ctx, + if (IS_ERR(obj)) + return -ENOENT; + ++ if (!nft_use_inc(&obj->use)) ++ return -EMFILE; ++ + nft_objref_priv(expr) = obj; +- obj->use++; + + return 0; + } +@@ -72,7 +74,7 @@ static void nft_objref_deactivate(const struct nft_ctx *ctx, + if (phase == NFT_TRANS_COMMIT) + return; + +- obj->use--; ++ nft_use_dec(&obj->use); + } + + static void nft_objref_activate(const struct nft_ctx *ctx, +@@ -80,7 +82,7 @@ static void nft_objref_activate(const struct nft_ctx *ctx, + { + struct nft_object *obj = nft_objref_priv(expr); + +- obj->use++; ++ nft_use_inc_restore(&obj->use); + } + + static struct nft_expr_type nft_objref_type; +-- +2.52.0 + +EOF + +cat << 'EOF' > patch2 +From 1689f25924ada8fe14a4a82c38925d04994c7142 Mon Sep 17 00:00:00 2001 +From: Pablo Neira Ayuso +Date: Wed, 28 Jun 2023 16:24:27 +0200 +Subject: [PATCH] netfilter: nf_tables: report use refcount overflow + +Overflow use refcount checks are not complete. + +Add helper function to deal with object reference counter tracking. +Report -EMFILE in case UINT_MAX is reached. + +nft_use_dec() splats in case that reference counter underflows, +which should not ever happen. + +Add nft_use_inc_restore() and nft_use_dec_restore() which are used +to restore reference counter from error and abort paths. + +Use u32 in nft_flowtable and nft_object since helper functions cannot +work on bitfields. + +Remove the few early incomplete checks now that the helper functions +are in place and used to check for refcount overflow. + +Fixes: 96518518cc41 ("netfilter: add nftables") +Signed-off-by: Pablo Neira Ayuso +--- + include/net/netfilter/nf_tables.h | 31 +++++- + net/netfilter/nf_tables_api.c | 163 ++++++++++++++++++------------ + net/netfilter/nft_flow_offload.c | 6 +- + net/netfilter/nft_immediate.c | 8 +- + net/netfilter/nft_objref.c | 8 +- + 5 files changed, 141 insertions(+), 75 deletions(-) + +diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h +index 84f2fd85fd5ae..640441a2f9266 100644 +--- a/include/net/netfilter/nf_tables.h ++++ b/include/net/netfilter/nf_tables.h +@@ -1211,6 +1211,29 @@ int __nft_release_basechain(struct nft_ctx *ctx); + + unsigned int nft_do_chain(struct nft_pktinfo *pkt, void *priv); + ++static inline bool nft_use_inc(u32 *use) ++{ ++ if (*use == UINT_MAX) ++ return false; ++ ++ (*use)++; ++ ++ return true; ++} ++ ++static inline void nft_use_dec(u32 *use) ++{ ++ WARN_ON_ONCE((*use)-- == 0); ++} ++ ++/* For error and abort path: restore use counter to previous state. */ ++static inline void nft_use_inc_restore(u32 *use) ++{ ++ WARN_ON_ONCE(!nft_use_inc(use)); ++} ++ ++#define nft_use_dec_restore nft_use_dec ++ + /** + * struct nft_table - nf_tables table + * +@@ -1296,8 +1319,8 @@ struct nft_object { + struct list_head list; + struct rhlist_head rhlhead; + struct nft_object_hash_key key; +- u32 genmask:2, +- use:30; ++ u32 genmask:2; ++ u32 use; + u64 handle; + u16 udlen; + u8 *udata; +@@ -1399,8 +1422,8 @@ struct nft_flowtable { + char *name; + int hooknum; + int ops_len; +- u32 genmask:2, +- use:30; ++ u32 genmask:2; ++ u32 use; + u64 handle; + /* runtime data below here */ + struct list_head hook_list ____cacheline_aligned; +diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c +index 9573a8fcad796..86b3c4de7f40d 100644 +--- a/net/netfilter/nf_tables_api.c ++++ b/net/netfilter/nf_tables_api.c +@@ -253,8 +253,10 @@ int nf_tables_bind_chain(const struct nft_ctx *ctx, struct nft_chain *chain) + if (chain->bound) + return -EBUSY; + ++ if (!nft_use_inc(&chain->use)) ++ return -EMFILE; ++ + chain->bound = true; +- chain->use++; + nft_chain_trans_bind(ctx, chain); + + return 0; +@@ -437,7 +439,7 @@ static int nft_delchain(struct nft_ctx *ctx) + if (IS_ERR(trans)) + return PTR_ERR(trans); + +- ctx->table->use--; ++ nft_use_dec(&ctx->table->use); + nft_deactivate_next(ctx->net, ctx->chain); + + return 0; +@@ -476,7 +478,7 @@ nf_tables_delrule_deactivate(struct nft_ctx *ctx, struct nft_rule *rule) + /* You cannot delete the same rule twice */ + if (nft_is_active_next(ctx->net, rule)) { + nft_deactivate_next(ctx->net, rule); +- ctx->chain->use--; ++ nft_use_dec(&ctx->chain->use); + return 0; + } + return -ENOENT; +@@ -644,7 +646,7 @@ static int nft_delset(const struct nft_ctx *ctx, struct nft_set *set) + nft_map_deactivate(ctx, set); + + nft_deactivate_next(ctx->net, set); +- ctx->table->use--; ++ nft_use_dec(&ctx->table->use); + + return err; + } +@@ -676,7 +678,7 @@ static int nft_delobj(struct nft_ctx *ctx, struct nft_object *obj) + return err; + + nft_deactivate_next(ctx->net, obj); +- ctx->table->use--; ++ nft_use_dec(&ctx->table->use); + + return err; + } +@@ -711,7 +713,7 @@ static int nft_delflowtable(struct nft_ctx *ctx, + return err; + + nft_deactivate_next(ctx->net, flowtable); +- ctx->table->use--; ++ nft_use_dec(&ctx->table->use); + + return err; + } +@@ -2396,9 +2398,6 @@ static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask, + struct nft_chain *chain; + int err; + +- if (table->use == UINT_MAX) +- return -EOVERFLOW; +- + if (nla[NFTA_CHAIN_HOOK]) { + struct nft_stats __percpu *stats = NULL; + struct nft_chain_hook hook = {}; +@@ -2494,6 +2493,11 @@ static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask, + if (err < 0) + goto err_destroy_chain; + ++ if (!nft_use_inc(&table->use)) { ++ err = -EMFILE; ++ goto err_use; ++ } ++ + trans = nft_trans_chain_add(ctx, NFT_MSG_NEWCHAIN); + if (IS_ERR(trans)) { + err = PTR_ERR(trans); +@@ -2510,10 +2514,11 @@ static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask, + goto err_unregister_hook; + } + +- table->use++; +- + return 0; ++ + err_unregister_hook: ++ nft_use_dec_restore(&table->use); ++err_use: + nf_tables_unregister_hook(net, table, chain); + err_destroy_chain: + nf_tables_chain_destroy(ctx); +@@ -3840,9 +3845,6 @@ static int nf_tables_newrule(struct sk_buff *skb, const struct nfnl_info *info, + return -EINVAL; + handle = nf_tables_alloc_handle(table); + +- if (chain->use == UINT_MAX) +- return -EOVERFLOW; +- + if (nla[NFTA_RULE_POSITION]) { + pos_handle = be64_to_cpu(nla_get_be64(nla[NFTA_RULE_POSITION])); + old_rule = __nft_rule_lookup(chain, pos_handle); +@@ -3936,6 +3938,11 @@ static int nf_tables_newrule(struct sk_buff *skb, const struct nfnl_info *info, + } + } + ++ if (!nft_use_inc(&chain->use)) { ++ err = -EMFILE; ++ goto err_release_rule; ++ } ++ + if (info->nlh->nlmsg_flags & NLM_F_REPLACE) { + err = nft_delrule(&ctx, old_rule); + if (err < 0) +@@ -3967,7 +3974,6 @@ static int nf_tables_newrule(struct sk_buff *skb, const struct nfnl_info *info, + } + } + kvfree(expr_info); +- chain->use++; + + if (flow) + nft_trans_flow_rule(trans) = flow; +@@ -3978,6 +3984,7 @@ static int nf_tables_newrule(struct sk_buff *skb, const struct nfnl_info *info, + return 0; + + err_destroy_flow_rule: ++ nft_use_dec_restore(&chain->use); + if (flow) + nft_flow_rule_destroy(flow); + err_release_rule: +@@ -5014,9 +5021,15 @@ static int nf_tables_newset(struct sk_buff *skb, const struct nfnl_info *info, + alloc_size = sizeof(*set) + size + udlen; + if (alloc_size < size || alloc_size > INT_MAX) + return -ENOMEM; ++ ++ if (!nft_use_inc(&table->use)) ++ return -EMFILE; ++ + set = kvzalloc(alloc_size, GFP_KERNEL_ACCOUNT); +- if (!set) +- return -ENOMEM; ++ if (!set) { ++ err = -ENOMEM; ++ goto err_alloc; ++ } + + name = nla_strdup(nla[NFTA_SET_NAME], GFP_KERNEL_ACCOUNT); + if (!name) { +@@ -5074,7 +5087,7 @@ static int nf_tables_newset(struct sk_buff *skb, const struct nfnl_info *info, + goto err_set_expr_alloc; + + list_add_tail_rcu(&set->list, &table->sets); +- table->use++; ++ + return 0; + + err_set_expr_alloc: +@@ -5086,6 +5099,9 @@ err_set_init: + kfree(set->name); + err_set_name: + kvfree(set); ++err_alloc: ++ nft_use_dec_restore(&table->use); ++ + return err; + } + +@@ -5224,9 +5240,6 @@ int nf_tables_bind_set(const struct nft_ctx *ctx, struct nft_set *set, + struct nft_set_binding *i; + struct nft_set_iter iter; + +- if (set->use == UINT_MAX) +- return -EOVERFLOW; +- + if (!list_empty(&set->bindings) && nft_set_is_anonymous(set)) + return -EBUSY; + +@@ -5254,10 +5267,12 @@ int nf_tables_bind_set(const struct nft_ctx *ctx, struct nft_set *set, + return iter.err; + } + bind: ++ if (!nft_use_inc(&set->use)) ++ return -EMFILE; ++ + binding->chain = ctx->chain; + list_add_tail_rcu(&binding->list, &set->bindings); + nft_set_trans_bind(ctx, set); +- set->use++; + + return 0; + } +@@ -5331,7 +5346,7 @@ void nf_tables_activate_set(const struct nft_ctx *ctx, struct nft_set *set) + nft_clear(ctx->net, set); + } + +- set->use++; ++ nft_use_inc_restore(&set->use); + } + EXPORT_SYMBOL_GPL(nf_tables_activate_set); + +@@ -5347,7 +5362,7 @@ void nf_tables_deactivate_set(const struct nft_ctx *ctx, struct nft_set *set, + else + list_del_rcu(&binding->list); + +- set->use--; ++ nft_use_dec(&set->use); + break; + case NFT_TRANS_PREPARE: + if (nft_set_is_anonymous(set)) { +@@ -5356,7 +5371,7 @@ void nf_tables_deactivate_set(const struct nft_ctx *ctx, struct nft_set *set, + + nft_deactivate_next(ctx->net, set); + } +- set->use--; ++ nft_use_dec(&set->use); + return; + case NFT_TRANS_ABORT: + case NFT_TRANS_RELEASE: +@@ -5364,7 +5379,7 @@ void nf_tables_deactivate_set(const struct nft_ctx *ctx, struct nft_set *set, + set->flags & (NFT_SET_MAP | NFT_SET_OBJECT)) + nft_map_deactivate(ctx, set); + +- set->use--; ++ nft_use_dec(&set->use); + fallthrough; + default: + nf_tables_unbind_set(ctx, set, binding, +@@ -6155,7 +6170,7 @@ void nft_set_elem_destroy(const struct nft_set *set, void *elem, + nft_set_elem_expr_destroy(&ctx, nft_set_ext_expr(ext)); + + if (nft_set_ext_exists(ext, NFT_SET_EXT_OBJREF)) +- (*nft_set_ext_obj(ext))->use--; ++ nft_use_dec(&(*nft_set_ext_obj(ext))->use); + kfree(elem); + } + EXPORT_SYMBOL_GPL(nft_set_elem_destroy); +@@ -6657,8 +6672,16 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, + set->objtype, genmask); + if (IS_ERR(obj)) { + err = PTR_ERR(obj); ++ obj = NULL; + goto err_parse_key_end; + } ++ ++ if (!nft_use_inc(&obj->use)) { ++ err = -EMFILE; ++ obj = NULL; ++ goto err_parse_key_end; ++ } ++ + err = nft_set_ext_add(&tmpl, NFT_SET_EXT_OBJREF); + if (err < 0) + goto err_parse_key_end; +@@ -6727,10 +6750,9 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, + if (flags) + *nft_set_ext_flags(ext) = flags; + +- if (obj) { ++ if (obj) + *nft_set_ext_obj(ext) = obj; +- obj->use++; +- } ++ + if (ulen > 0) { + if (nft_set_ext_check(&tmpl, NFT_SET_EXT_USERDATA, ulen) < 0) { + err = -EINVAL; +@@ -6798,12 +6820,13 @@ err_element_clash: + kfree(trans); + err_elem_free: + nf_tables_set_elem_destroy(ctx, set, elem.priv); +- if (obj) +- obj->use--; + err_parse_data: + if (nla[NFTA_SET_ELEM_DATA] != NULL) + nft_data_release(&elem.data.val, desc.type); + err_parse_key_end: ++ if (obj) ++ nft_use_dec_restore(&obj->use); ++ + nft_data_release(&elem.key_end.val, NFT_DATA_VALUE); + err_parse_key: + nft_data_release(&elem.key.val, NFT_DATA_VALUE); +@@ -6883,7 +6906,7 @@ void nft_data_hold(const struct nft_data *data, enum nft_data_types type) + case NFT_JUMP: + case NFT_GOTO: + chain = data->verdict.chain; +- chain->use++; ++ nft_use_inc_restore(&chain->use); + break; + } + } +@@ -6898,7 +6921,7 @@ static void nft_setelem_data_activate(const struct net *net, + if (nft_set_ext_exists(ext, NFT_SET_EXT_DATA)) + nft_data_hold(nft_set_ext_data(ext), set->dtype); + if (nft_set_ext_exists(ext, NFT_SET_EXT_OBJREF)) +- (*nft_set_ext_obj(ext))->use++; ++ nft_use_inc_restore(&(*nft_set_ext_obj(ext))->use); + } + + static void nft_setelem_data_deactivate(const struct net *net, +@@ -6910,7 +6933,7 @@ static void nft_setelem_data_deactivate(const struct net *net, + if (nft_set_ext_exists(ext, NFT_SET_EXT_DATA)) + nft_data_release(nft_set_ext_data(ext), set->dtype); + if (nft_set_ext_exists(ext, NFT_SET_EXT_OBJREF)) +- (*nft_set_ext_obj(ext))->use--; ++ nft_use_dec(&(*nft_set_ext_obj(ext))->use); + } + + static int nft_del_setelem(struct nft_ctx *ctx, struct nft_set *set, +@@ -7453,9 +7476,14 @@ static int nf_tables_newobj(struct sk_buff *skb, const struct nfnl_info *info, + + nft_ctx_init(&ctx, net, skb, info->nlh, family, table, NULL, nla); + ++ if (!nft_use_inc(&table->use)) ++ return -EMFILE; ++ + type = nft_obj_type_get(net, objtype); +- if (IS_ERR(type)) +- return PTR_ERR(type); ++ if (IS_ERR(type)) { ++ err = PTR_ERR(type); ++ goto err_type; ++ } + + obj = nft_obj_init(&ctx, type, nla[NFTA_OBJ_DATA]); + if (IS_ERR(obj)) { +@@ -7489,7 +7517,7 @@ static int nf_tables_newobj(struct sk_buff *skb, const struct nfnl_info *info, + goto err_obj_ht; + + list_add_tail_rcu(&obj->list, &table->objects); +- table->use++; ++ + return 0; + err_obj_ht: + /* queued in transaction log */ +@@ -7505,6 +7533,9 @@ err_strdup: + kfree(obj); + err_init: + module_put(type->owner); ++err_type: ++ nft_use_dec_restore(&table->use); ++ + return err; + } + +@@ -7906,7 +7937,7 @@ void nf_tables_deactivate_flowtable(const struct nft_ctx *ctx, + case NFT_TRANS_PREPARE: + case NFT_TRANS_ABORT: + case NFT_TRANS_RELEASE: +- flowtable->use--; ++ nft_use_dec(&flowtable->use); + fallthrough; + default: + return; +@@ -8260,9 +8291,14 @@ static int nf_tables_newflowtable(struct sk_buff *skb, + + nft_ctx_init(&ctx, net, skb, info->nlh, family, table, NULL, nla); + ++ if (!nft_use_inc(&table->use)) ++ return -EMFILE; ++ + flowtable = kzalloc(sizeof(*flowtable), GFP_KERNEL_ACCOUNT); +- if (!flowtable) +- return -ENOMEM; ++ if (!flowtable) { ++ err = -ENOMEM; ++ goto flowtable_alloc; ++ } + + flowtable->table = table; + flowtable->handle = nf_tables_alloc_handle(table); +@@ -8317,7 +8353,6 @@ static int nf_tables_newflowtable(struct sk_buff *skb, + goto err5; + + list_add_tail_rcu(&flowtable->list, &table->flowtables); +- table->use++; + + return 0; + err5: +@@ -8334,6 +8369,9 @@ err2: + kfree(flowtable->name); + err1: + kfree(flowtable); ++flowtable_alloc: ++ nft_use_dec_restore(&table->use); ++ + return err; + } + +@@ -9713,7 +9751,7 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb) + */ + if (nft_set_is_anonymous(nft_trans_set(trans)) && + !list_empty(&nft_trans_set(trans)->bindings)) +- trans->ctx.table->use--; ++ nft_use_dec(&trans->ctx.table->use); + } + nf_tables_set_notify(&trans->ctx, nft_trans_set(trans), + NFT_MSG_NEWSET, GFP_KERNEL); +@@ -9943,7 +9981,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + nft_trans_destroy(trans); + break; + } +- trans->ctx.table->use--; ++ nft_use_dec_restore(&trans->ctx.table->use); + nft_chain_del(trans->ctx.chain); + nf_tables_unregister_hook(trans->ctx.net, + trans->ctx.table, +@@ -9956,7 +9994,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + list_splice(&nft_trans_chain_hooks(trans), + &nft_trans_basechain(trans)->hook_list); + } else { +- trans->ctx.table->use++; ++ nft_use_inc_restore(&trans->ctx.table->use); + nft_clear(trans->ctx.net, trans->ctx.chain); + } + nft_trans_destroy(trans); +@@ -9966,7 +10004,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + nft_trans_destroy(trans); + break; + } +- trans->ctx.chain->use--; ++ nft_use_dec_restore(&trans->ctx.chain->use); + list_del_rcu(&nft_trans_rule(trans)->list); + nft_rule_expr_deactivate(&trans->ctx, + nft_trans_rule(trans), +@@ -9976,7 +10014,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + break; + case NFT_MSG_DELRULE: + case NFT_MSG_DESTROYRULE: +- trans->ctx.chain->use++; ++ nft_use_inc_restore(&trans->ctx.chain->use); + nft_clear(trans->ctx.net, nft_trans_rule(trans)); + nft_rule_expr_activate(&trans->ctx, nft_trans_rule(trans)); + if (trans->ctx.chain->flags & NFT_CHAIN_HW_OFFLOAD) +@@ -9989,7 +10027,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + nft_trans_destroy(trans); + break; + } +- trans->ctx.table->use--; ++ nft_use_dec_restore(&trans->ctx.table->use); + if (nft_trans_set_bound(trans)) { + nft_trans_destroy(trans); + break; +@@ -9998,7 +10036,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + break; + case NFT_MSG_DELSET: + case NFT_MSG_DESTROYSET: +- trans->ctx.table->use++; ++ nft_use_inc_restore(&trans->ctx.table->use); + nft_clear(trans->ctx.net, nft_trans_set(trans)); + if (nft_trans_set(trans)->flags & (NFT_SET_MAP | NFT_SET_OBJECT)) + nft_map_activate(&trans->ctx, nft_trans_set(trans)); +@@ -10042,13 +10080,13 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + nft_obj_destroy(&trans->ctx, nft_trans_obj_newobj(trans)); + nft_trans_destroy(trans); + } else { +- trans->ctx.table->use--; ++ nft_use_dec_restore(&trans->ctx.table->use); + nft_obj_del(nft_trans_obj(trans)); + } + break; + case NFT_MSG_DELOBJ: + case NFT_MSG_DESTROYOBJ: +- trans->ctx.table->use++; ++ nft_use_inc_restore(&trans->ctx.table->use); + nft_clear(trans->ctx.net, nft_trans_obj(trans)); + nft_trans_destroy(trans); + break; +@@ -10057,7 +10095,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + nft_unregister_flowtable_net_hooks(net, + &nft_trans_flowtable_hooks(trans)); + } else { +- trans->ctx.table->use--; ++ nft_use_dec_restore(&trans->ctx.table->use); + list_del_rcu(&nft_trans_flowtable(trans)->list); + nft_unregister_flowtable_net_hooks(net, + &nft_trans_flowtable(trans)->hook_list); +@@ -10069,7 +10107,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + list_splice(&nft_trans_flowtable_hooks(trans), + &nft_trans_flowtable(trans)->hook_list); + } else { +- trans->ctx.table->use++; ++ nft_use_inc_restore(&trans->ctx.table->use); + nft_clear(trans->ctx.net, nft_trans_flowtable(trans)); + } + nft_trans_destroy(trans); +@@ -10518,8 +10556,9 @@ static int nft_verdict_init(const struct nft_ctx *ctx, struct nft_data *data, + if (desc->flags & NFT_DATA_DESC_SETELEM && + chain->flags & NFT_CHAIN_BINDING) + return -EINVAL; ++ if (!nft_use_inc(&chain->use)) ++ return -EMFILE; + +- chain->use++; + data->verdict.chain = chain; + break; + } +@@ -10537,7 +10576,7 @@ static void nft_verdict_uninit(const struct nft_data *data) + case NFT_JUMP: + case NFT_GOTO: + chain = data->verdict.chain; +- chain->use--; ++ nft_use_dec(&chain->use); + break; + } + } +@@ -10706,11 +10745,11 @@ int __nft_release_basechain(struct nft_ctx *ctx) + nf_tables_unregister_hook(ctx->net, ctx->chain->table, ctx->chain); + list_for_each_entry_safe(rule, nr, &ctx->chain->rules, list) { + list_del(&rule->list); +- ctx->chain->use--; ++ nft_use_dec(&ctx->chain->use); + nf_tables_rule_release(ctx, rule); + } + nft_chain_del(ctx->chain); +- ctx->table->use--; ++ nft_use_dec(&ctx->table->use); + nf_tables_chain_destroy(ctx); + + return 0; +@@ -10760,18 +10799,18 @@ static void __nft_release_table(struct net *net, struct nft_table *table) + ctx.chain = chain; + list_for_each_entry_safe(rule, nr, &chain->rules, list) { + list_del(&rule->list); +- chain->use--; ++ nft_use_dec(&chain->use); + nf_tables_rule_release(&ctx, rule); + } + } + list_for_each_entry_safe(flowtable, nf, &table->flowtables, list) { + list_del(&flowtable->list); +- table->use--; ++ nft_use_dec(&table->use); + nf_tables_flowtable_destroy(flowtable); + } + list_for_each_entry_safe(set, ns, &table->sets, list) { + list_del(&set->list); +- table->use--; ++ nft_use_dec(&table->use); + if (set->flags & (NFT_SET_MAP | NFT_SET_OBJECT)) + nft_map_deactivate(&ctx, set); + +@@ -10779,13 +10818,13 @@ static void __nft_release_table(struct net *net, struct nft_table *table) + } + list_for_each_entry_safe(obj, ne, &table->objects, list) { + nft_obj_del(obj); +- table->use--; ++ nft_use_dec(&table->use); + nft_obj_destroy(&ctx, obj); + } + list_for_each_entry_safe(chain, nc, &table->chains, list) { + ctx.chain = chain; + nft_chain_del(chain); +- table->use--; ++ nft_use_dec(&table->use); + nf_tables_chain_destroy(&ctx); + } + nf_tables_table_destroy(&ctx); +diff --git a/net/netfilter/nft_flow_offload.c b/net/netfilter/nft_flow_offload.c +index 5ef9146e74ad9..ab3362c483b4a 100644 +--- a/net/netfilter/nft_flow_offload.c ++++ b/net/netfilter/nft_flow_offload.c +@@ -408,8 +408,10 @@ static int nft_flow_offload_init(const struct nft_ctx *ctx, + if (IS_ERR(flowtable)) + return PTR_ERR(flowtable); + ++ if (!nft_use_inc(&flowtable->use)) ++ return -EMFILE; ++ + priv->flowtable = flowtable; +- flowtable->use++; + + return nf_ct_netns_get(ctx->net, ctx->family); + } +@@ -428,7 +430,7 @@ static void nft_flow_offload_activate(const struct nft_ctx *ctx, + { + struct nft_flow_offload *priv = nft_expr_priv(expr); + +- priv->flowtable->use++; ++ nft_use_inc_restore(&priv->flowtable->use); + } + + static void nft_flow_offload_destroy(const struct nft_ctx *ctx, +diff --git a/net/netfilter/nft_immediate.c b/net/netfilter/nft_immediate.c +index 3d76ebfe8939b..407d7197f75bb 100644 +--- a/net/netfilter/nft_immediate.c ++++ b/net/netfilter/nft_immediate.c +@@ -159,7 +159,7 @@ static void nft_immediate_deactivate(const struct nft_ctx *ctx, + default: + nft_chain_del(chain); + chain->bound = false; +- chain->table->use--; ++ nft_use_dec(&chain->table->use); + break; + } + break; +@@ -198,7 +198,7 @@ static void nft_immediate_destroy(const struct nft_ctx *ctx, + * let the transaction records release this chain and its rules. + */ + if (chain->bound) { +- chain->use--; ++ nft_use_dec(&chain->use); + break; + } + +@@ -206,9 +206,9 @@ static void nft_immediate_destroy(const struct nft_ctx *ctx, + chain_ctx = *ctx; + chain_ctx.chain = chain; + +- chain->use--; ++ nft_use_dec(&chain->use); + list_for_each_entry_safe(rule, n, &chain->rules, list) { +- chain->use--; ++ nft_use_dec(&chain->use); + list_del(&rule->list); + nf_tables_rule_destroy(&chain_ctx, rule); + } +diff --git a/net/netfilter/nft_objref.c b/net/netfilter/nft_objref.c +index a48dd5b5d45b1..509011b1ef597 100644 +--- a/net/netfilter/nft_objref.c ++++ b/net/netfilter/nft_objref.c +@@ -41,8 +41,10 @@ static int nft_objref_init(const struct nft_ctx *ctx, + if (IS_ERR(obj)) + return -ENOENT; + ++ if (!nft_use_inc(&obj->use)) ++ return -EMFILE; ++ + nft_objref_priv(expr) = obj; +- obj->use++; + + return 0; + } +@@ -72,7 +74,7 @@ static void nft_objref_deactivate(const struct nft_ctx *ctx, + if (phase == NFT_TRANS_COMMIT) + return; + +- obj->use--; ++ nft_use_dec(&obj->use); + } + + static void nft_objref_activate(const struct nft_ctx *ctx, +@@ -80,7 +82,7 @@ static void nft_objref_activate(const struct nft_ctx *ctx, + { + struct nft_object *obj = nft_objref_priv(expr); + +- obj->use++; ++ nft_use_inc_restore(&obj->use); + } + + static const struct nft_expr_ops nft_objref_ops = { +-- +2.52.0 + +EOF + +cat << 'EOF' > expected +diff -u b/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c +--- b/net/netfilter/nf_tables_api.c ++++ b/net/netfilter/nf_tables_api.c +@@ -2297,6 +2297,6 @@ +- struct nft_chain *chain; ++ struct nft_rule **rules; + int err; + + if (nla[NFTA_CHAIN_HOOK]) { + struct nft_stats __percpu *stats = NULL; +- struct nft_chain_hook hook = {}; ++ struct nft_chain_hook hook; +@@ -4790,13 +4790,13 @@ +- +- if (!nft_use_inc(&table->use)) +- return -EMFILE; +- + alloc_size = sizeof(*set) + size + udlen; + if (alloc_size < size || alloc_size > INT_MAX) + return -ENOMEM; +- set = kvzalloc(alloc_size, GFP_KERNEL_ACCOUNT); ++ set = kvzalloc(alloc_size, GFP_KERNEL); + if (!set) + return -ENOMEM; + +- name = nla_strdup(nla[NFTA_SET_NAME], GFP_KERNEL_ACCOUNT); ++ ++ if (!nft_use_inc(&table->use)) ++ return -EMFILE; ++ ++ name = nla_strdup(nla[NFTA_SET_NAME], GFP_KERNEL); + if (!name) { +@@ -5026,8 +5037,10 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + if (alloc_size < size || alloc_size > INT_MAX) + return -ENOMEM; + set = kvzalloc(alloc_size, GFP_KERNEL_ACCOUNT); +- if (!set) +- return -ENOMEM; ++ if (!set) { ++ err = -ENOMEM; ++ goto err_alloc; ++ } + + name = nla_strdup(nla[NFTA_SET_NAME], GFP_KERNEL_ACCOUNT); + if (!name) { +@@ -8040,12 +8040,12 @@ +- if (!nft_use_inc(&table->use)) +- return -EMFILE; +- + + nft_ctx_init(&ctx, net, skb, info->nlh, family, table, NULL, nla); + +- flowtable = kzalloc(sizeof(*flowtable), GFP_KERNEL_ACCOUNT); ++ flowtable = kzalloc(sizeof(*flowtable), GFP_KERNEL); + if (!flowtable) + return -ENOMEM; + + flowtable->table = table; + flowtable->handle = nf_tables_alloc_handle(table); ++ if (!nft_use_inc(&table->use)) ++ return -EMFILE; ++ +@@ -8293,8 +8327,10 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + nft_ctx_init(&ctx, net, skb, info->nlh, family, table, NULL, nla); + + flowtable = kzalloc(sizeof(*flowtable), GFP_KERNEL_ACCOUNT); +- if (!flowtable) +- return -ENOMEM; ++ if (!flowtable) { ++ err = -ENOMEM; ++ goto flowtable_alloc; ++ } + + flowtable->table = table; + flowtable->handle = nf_tables_alloc_handle(table); +@@ -9654,13 +9654,13 @@ + nft_chain_del(trans->ctx.chain); + nf_tables_unregister_hook(trans->ctx.net, + trans->ctx.table, +- list_splice(&nft_trans_chain_hooks(trans), +- &nft_trans_basechain(trans)->hook_list); +- } else { +- trans->ctx.table->use++; +- nft_clear(trans->ctx.net, trans->ctx.chain); +- } ++ break; ++ case NFT_MSG_DELCHAIN: ++ case NFT_MSG_DESTROYCHAIN: ++ trans->ctx.table->use++; ++ nft_clear(trans->ctx.net, trans->ctx.chain); + nft_trans_destroy(trans); ++ break; + nft_trans_destroy(trans); + break; + } +@@ -9672,7 +9710,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context + nft_trans_destroy(trans); + break; + } +- trans->ctx.chain->use--; ++ nft_use_dec_restore(&trans->ctx.chain->use); + list_del_rcu(&nft_trans_rule(trans)->list); + nft_rule_expr_deactivate(&trans->ctx, + nft_trans_rule(trans), +@@ -9682,7 +9720,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context + break; + case NFT_MSG_DELRULE: + case NFT_MSG_DESTROYRULE: +- trans->ctx.chain->use++; ++ nft_use_inc_restore(&trans->ctx.chain->use); + nft_clear(trans->ctx.net, nft_trans_rule(trans)); + nft_rule_expr_activate(&trans->ctx, nft_trans_rule(trans)); + if (trans->ctx.chain->flags & NFT_CHAIN_HW_OFFLOAD) +@@ -9695,7 +9733,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context + nft_trans_destroy(trans); + break; + } +- trans->ctx.table->use--; ++ nft_use_dec_restore(&trans->ctx.table->use); + if (nft_trans_set_bound(trans)) { + nft_trans_destroy(trans); + break; +@@ -9748,9 +9786,9 @@ INTERDIFF: rejected hunk from patch1, cannot diff context + nft_obj_destroy(&trans->ctx, nft_trans_obj_newobj(trans)); + nft_trans_destroy(trans); + } else { +- trans->ctx.table->use--; ++ nft_use_dec_restore(&trans->ctx.table->use); + nft_obj_del(nft_trans_obj(trans)); + } + break; + case NFT_MSG_DELOBJ: + case NFT_MSG_DESTROYOBJ: +@@ -9754,7 +9792,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context + break; + case NFT_MSG_DELOBJ: + case NFT_MSG_DESTROYOBJ: +- trans->ctx.table->use++; ++ nft_use_inc_restore(&trans->ctx.table->use); + nft_clear(trans->ctx.net, nft_trans_obj(trans)); + nft_trans_destroy(trans); + break; +@@ -10000,7 +10038,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + nft_trans_destroy(trans); + break; + } +- trans->ctx.chain->use--; ++ nft_use_dec_restore(&trans->ctx.chain->use); + list_del_rcu(&nft_trans_rule(trans)->list); + nft_rule_expr_deactivate(&trans->ctx, + nft_trans_rule(trans), +@@ -10010,7 +10048,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + break; + case NFT_MSG_DELRULE: + case NFT_MSG_DESTROYRULE: +- trans->ctx.chain->use++; ++ nft_use_inc_restore(&trans->ctx.chain->use); + nft_clear(trans->ctx.net, nft_trans_rule(trans)); + nft_rule_expr_activate(&trans->ctx, nft_trans_rule(trans)); + if (trans->ctx.chain->flags & NFT_CHAIN_HW_OFFLOAD) +@@ -10023,7 +10061,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + nft_trans_destroy(trans); + break; + } +- trans->ctx.table->use--; ++ nft_use_dec_restore(&trans->ctx.table->use); + if (nft_trans_set_bound(trans)) { + nft_trans_destroy(trans); + break; +@@ -10032,7 +10070,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + break; + case NFT_MSG_DELSET: + case NFT_MSG_DESTROYSET: +- trans->ctx.table->use++; ++ nft_use_inc_restore(&trans->ctx.table->use); + nft_clear(trans->ctx.net, nft_trans_set(trans)); + if (nft_trans_set(trans)->flags & (NFT_SET_MAP | NFT_SET_OBJECT)) + nft_map_activate(&trans->ctx, nft_trans_set(trans)); +@@ -10076,9 +10114,9 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + nft_obj_destroy(&trans->ctx, nft_trans_obj_newobj(trans)); + nft_trans_destroy(trans); + } else { +- trans->ctx.table->use--; ++ nft_use_dec_restore(&trans->ctx.table->use); + nft_obj_del(nft_trans_obj(trans)); + } + break; + case NFT_MSG_DELOBJ: + case NFT_MSG_DESTROYOBJ: +@@ -10082,7 +10120,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + break; + case NFT_MSG_DELOBJ: + case NFT_MSG_DESTROYOBJ: +- trans->ctx.table->use++; ++ nft_use_inc_restore(&trans->ctx.table->use); + nft_clear(trans->ctx.net, nft_trans_obj(trans)); + nft_trans_destroy(trans); + break; +@@ -10091,7 +10129,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + nft_unregister_flowtable_net_hooks(net, + &nft_trans_flowtable_hooks(trans)); + } else { +- trans->ctx.table->use--; ++ nft_use_dec_restore(&trans->ctx.table->use); + list_del_rcu(&nft_trans_flowtable(trans)->list); + nft_unregister_flowtable_net_hooks(net, + &nft_trans_flowtable(trans)->hook_list); +@@ -10103,7 +10141,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + list_splice(&nft_trans_flowtable_hooks(trans), + &nft_trans_flowtable(trans)->hook_list); + } else { +- trans->ctx.table->use++; ++ nft_use_inc_restore(&trans->ctx.table->use); + nft_clear(trans->ctx.net, nft_trans_flowtable(trans)); + } + nft_trans_destroy(trans); +@@ -10220,4 +10220,4 @@ + + data->verdict.chain = chain; + break; +- } ++ default: +diff -u b/net/netfilter/nft_objref.c b/net/netfilter/nft_objref.c +--- b/net/netfilter/nft_objref.c ++++ b/net/netfilter/nft_objref.c +@@ -84,4 +84,4 @@ + nft_use_inc_restore(&obj->use); + } + +-static const struct nft_expr_ops nft_objref_ops = { ++static struct nft_expr_type nft_objref_type; +EOF + +${INTERDIFF} --fuzzy=3 patch1 patch2 2>errors >output +[ -s errors ] && exit 1 + +cmp output expected || exit 1