commit ad0c76036a28ad13ef1a536c7b7da4b29028c763 Author: Jacob Welsh AuthorDate: Tue Oct 28 00:51:30 2025 +0000 Commit: Jacob Welsh CommitDate: Tue Oct 28 01:14:56 2025 +0000 busybox/findutils: fix loss of grep output after 2^31 input lines and extend line counters to 64 bits. grep dropped matching lines after counter overflow, even without the -n, -m or -c modes which use the counts, due to the awkward (linenum < 1) exit in print_line. This change addresses the original problem of leaking context lines in a stronger way, by resetting the context buffer between files. Thus, wrapping line counters no longer affect the modes that don't use them; for the rest, the counters and their displays expand to 64-bit unsigned. The initial allocation of the context buffer now takes advantage of calloc, saving a dedicated overflow check. Finally, this addresses a potential format string vulnerability in the usage_pod build program, and a local pointer with non-obvious initialization in grep_file, giving a warning-free grep rebuild. Text size +42 bytes on amd64. diff --git a/base/busybox/applets/usage_pod.c b/base/busybox/applets/usage_pod.c index 0b1c4aa..a67e8b4 100644 --- a/base/busybox/applets/usage_pod.c +++ b/base/busybox/applets/usage_pod.c @@ -71,7 +71,7 @@ int main(void) } else { printf(", "); } - printf(usage_array[i].aname); + printf("%s", usage_array[i].aname); col += len2; } printf("\n\n"); diff --git a/base/busybox/findutils/grep.c b/base/busybox/findutils/grep.c index b962138..f87f890 100644 --- a/base/busybox/findutils/grep.c +++ b/base/busybox/findutils/grep.c @@ -180,7 +180,7 @@ enum { #define NUL_DELIMITED (option_mask32 & OPT_z) struct globals { - int max_matches; + unsigned max_matches; #if !ENABLE_EXTRA_COMPAT int reflags; #else @@ -195,7 +195,7 @@ struct globals { int lines_after; char **before_buf; IF_EXTRA_COMPAT(size_t *before_buf_size;) - int last_line_printed; + uint64_t last_line_printed; #endif /* globals used internally */ llist_t *pattern_head; /* growable list of patterns to match */ @@ -254,13 +254,9 @@ typedef struct grep_list_data_t { #define print_line(line, line_len, linenum, decoration) \ print_line(line, linenum, decoration) #endif -static void print_line(const char *line, size_t line_len, int linenum, char decoration) +static void print_line(const char *line, size_t line_len, uint64_t linenum, char decoration) { #if ENABLE_FEATURE_GREP_CONTEXT - /* Happens when we go to next file, immediately hit match - * and try to print prev context... from prev file! Don't do it */ - if (linenum < 1) - return; /* possibly print the little '--' separator */ if ((lines_before || lines_after) && did_print_line && last_line_printed != linenum - 1 @@ -274,7 +270,7 @@ static void print_line(const char *line, size_t line_len, int linenum, char deco if (print_filename) printf("%s%c", cur_file, decoration); if (PRINT_LINE_NUM) - printf("%i%c", linenum, decoration); + printf("%"PRIu64"%c", linenum, decoration); /* Emulate weird GNU grep behavior with -ov */ if ((option_mask32 & (OPT_v|OPT_o)) != (OPT_v|OPT_o)) { #if !ENABLE_EXTRA_COMPAT @@ -310,8 +306,10 @@ static ssize_t FAST_FUNC bb_getline(char **line_ptr, size_t *line_alloc_len, FIL static int grep_file(FILE *file) { smalluint found; - int linenum = 0; - int nmatches = 0; + /* Input line count can easily exceed 2^32. */ + uint64_t linenum = 0; + /* grep -c may need to report a match count exceeding 2^32. */ + uint64_t nmatches = 0; #if !ENABLE_EXTRA_COMPAT char *line; #else @@ -325,6 +323,12 @@ static int grep_file(FILE *file) int print_n_lines_after = 0; int curpos = 0; /* track where we are in the circular 'before' buffer */ int idx = 0; /* used for iteration through the circular buffer */ + + /* clear out any lines buffered from a prior file */ + for (idx = 0; idx < lines_before; ++idx) { + free(before_buf[idx]); + before_buf[idx] = NULL; + } #else enum { print_n_lines_after = 0 }; #endif @@ -337,7 +341,7 @@ static int grep_file(FILE *file) #endif ) { llist_t *pattern_ptr = pattern_head; - grep_list_data_t *gl = gl; /* for gcc */ + grep_list_data_t *gl = NULL; linenum++; found = 0; @@ -483,7 +487,8 @@ static int grep_file(FILE *file) #if ENABLE_FEATURE_GREP_CONTEXT /* Were we printing context and saw next (unwanted) match? */ - if ((option_mask32 & OPT_m) && nmatches > max_matches) + /* Per getopt32, the range of max_matches is 0..INT_MAX, so for this option we don't need 64-bit nmatches. */ + if ((option_mask32 & OPT_m) && (unsigned) nmatches > max_matches) break; #endif @@ -495,11 +500,10 @@ static int grep_file(FILE *file) /* if we were told to print 'before' lines and there is at least * one line in the circular buffer, print them */ if (lines_before && before_buf[prevpos] != NULL) { - int first_buf_entry_line_num = linenum - lines_before; + /* can underflow at start of file but then gets restored */ + uint64_t first_buf_entry_line_num = linenum - lines_before; - /* advance to the first entry in the circular buffer, and - * figure out the line number is of the first line in the - * buffer */ + /* advance to the first entry in the circular buffer, and figure out its line number */ idx = curpos; while (before_buf[idx] == NULL) { idx = (idx + 1) % lines_before; @@ -581,7 +585,7 @@ static int grep_file(FILE *file) /* Did we print all context after last requested match? */ if ((option_mask32 & OPT_m) && !print_n_lines_after - && nmatches == max_matches + && (unsigned) nmatches == max_matches /* 32-bit ok as noted above */ ) { break; } @@ -594,7 +598,7 @@ static int grep_file(FILE *file) if (PRINT_MATCH_COUNTS) { if (print_filename) printf("%s:", cur_file); - printf("%d\n", nmatches); + printf("%"PRIu64"\n", nmatches); } /* grep -L: print just the filename */ @@ -605,7 +609,8 @@ static int grep_file(FILE *file) puts(cur_file); } - return nmatches; + /* No need for 64-bit result as it's only used to see if there were any matches. */ + return nmatches != 0; } #if ENABLE_FEATURE_CLEAN_UP @@ -658,7 +663,7 @@ static int FAST_FUNC file_action_grep(const char *filename, return 0; } cur_file = filename; - *(int*)matched += grep_file(file); + *(int*)matched |= grep_file(file); fclose(file); return 1; } @@ -710,11 +715,8 @@ int grep_main(int argc UNUSED_PARAM, char **argv) lines_before = 0; lines_after = 0; } else if (lines_before > 0) { - if (lines_before > INT_MAX / sizeof(long long)) - lines_before = INT_MAX / sizeof(long long); - /* overflow in (lines_before * sizeof(x)) is prevented (above) */ - before_buf = xzalloc(lines_before * sizeof(before_buf[0])); - IF_EXTRA_COMPAT(before_buf_size = xzalloc(lines_before * sizeof(before_buf_size[0]));) + before_buf = xcalloc(lines_before, sizeof(before_buf[0])); + IF_EXTRA_COMPAT(before_buf_size = xcalloc(lines_before, sizeof(before_buf_size[0]));) } #else /* with auto sanity checks */ @@ -807,7 +809,7 @@ int grep_main(int argc UNUSED_PARAM, char **argv) if (stat(cur_file, &st) == 0 && S_ISDIR(st.st_mode)) { if (!(option_mask32 & OPT_h)) print_filename = 1; - matched += grep_dir(cur_file); + matched |= grep_dir(cur_file); goto grep_done; } } @@ -820,7 +822,7 @@ int grep_main(int argc UNUSED_PARAM, char **argv) continue; } } - matched += grep_file(file); + matched |= grep_file(file); fclose_if_not_stdin(file); grep_done: ; } while (*argv && *++argv);