Linux SPARSE checker discussions
 help / color / mirror / Atom feed
From: Bart Van Assche <bvanassche@acm.org>
To: linux-sparse@vger.kernel.org
Cc: Linus Torvalds <torvalds@linux-foundation.org>,
	Bart Van Assche <bvanassche@acm.org>
Subject: [PATCH 07/73] pre-process: Fix macro expansion and include directive handling
Date: Mon,  9 Nov 2026 12:31:53 -0800	[thread overview]
Message-ID: <20261109203259.362845-8-bvanassche@acm.org> (raw)
In-Reply-To: <20261109203259.362845-1-bvanassche@acm.org>

Fix buffer bounds, unterminated macro arguments, and include path checks
in pre-process.c:
- Handle TOKEN_STREAMEND and TOKEN_STREAMBEGIN on unterminated macro
  argument lists and bound token-pasting buffers in combine().
- Widen token_name_sequence() to PATH_MAX and check remaining capacity
  with pointer subtraction to prevent buffer and pointer overflows.
- Reject directory paths in try_include() via fstat() and diagnose
  incomplete defined() expressions in #if directives.
- Add regression tests in
  validation/preprocessor/preprocess-crash-fixes.c and
  validation/preprocessor/preprocess-errors-and-limits.c.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 pre-process.c                                 | 83 +++++++++++++++----
 .../preprocessor/preprocess-crash-fixes.c     | 25 ++++++
 .../preprocess-errors-and-limits.c            | 33 ++++++++
 3 files changed, 123 insertions(+), 18 deletions(-)
 create mode 100644 validation/preprocessor/preprocess-crash-fixes.c
 create mode 100644 validation/preprocessor/preprocess-errors-and-limits.c

diff --git a/pre-process.c b/pre-process.c
index 7b6555677570..ca019d704a10 100644
--- a/pre-process.c
+++ b/pre-process.c
@@ -35,6 +35,7 @@
 #include <fcntl.h>
 #include <limits.h>
 #include <time.h>
+#include <sys/stat.h>
 
 #include "lib.h"
 #include "allocate.h"
@@ -107,8 +108,12 @@ static int expand(struct token **, struct symbol *);
 
 static void replace_with_string(struct token *token, const char *str)
 {
-	int size = strlen(str) + 1;
-	struct string *s = __alloc_string(size);
+	size_t size = strlen(str) + 1;
+	struct string *s;
+
+	if (size > INT_MAX - sizeof(struct string))
+		die("string too large");
+	s = __alloc_string(size);
 
 	s->length = size;
 	memcpy(s->data, str, size);
@@ -312,6 +317,13 @@ struct arg {
 	int n_str;
 };
 
+static inline bool stream_end_token(const struct token *t)
+{
+	return eof_token(t) ||
+	       token_type(t) == TOKEN_STREAMEND ||
+	       token_type(t) == TOKEN_STREAMBEGIN;
+}
+
 static int collect_arguments(struct token *start, struct token *arglist, struct arg *args, struct token *what)
 {
 	int wanted = arglist->count.normal;
@@ -322,7 +334,7 @@ static int collect_arguments(struct token *start, struct token *arglist, struct
 
 	if (!wanted) {
 		next = collect_arg(start, 0, &what->pos);
-		if (eof_token(next))
+		if (stream_end_token(next))
 			goto Eclosing;
 		if (!eof_token(start->next) || !match_op(next, ')')) {
 			count++;
@@ -332,7 +344,7 @@ static int collect_arguments(struct token *start, struct token *arglist, struct
 		for (count = 0; count < wanted; count++) {
 			struct argcount *p = &arglist->next->count;
 			next = collect_arg(start, p->vararg, &what->pos);
-			if (eof_token(next))
+			if (stream_end_token(next))
 				goto Eclosing;
 			if (p->vararg && wanted == 1 && eof_token(start->next))
 				break;
@@ -373,7 +385,7 @@ Emany:
 		next = collect_arg(next, 0, &what->pos);
 		count++;
 	}
-	if (eof_token(next))
+	if (stream_end_token(next))
 		goto Eclosing;
 	sparse_error(what->pos, "macro \"%s\" passed %d arguments, but takes just %d",
 		show_token(what), count, wanted);
@@ -382,7 +394,10 @@ Eclosing:
 	sparse_error(what->pos, "unterminated argument list invoking macro \"%s\"",
 		show_token(what));
 out:
-	what->next = next->next;
+	if (stream_end_token(next))
+		what->next = next;
+	else
+		what->next = next->next;
 	return 0;
 }
 
@@ -499,12 +514,22 @@ static enum token_type combine(struct token *left, struct token *right, char *p)
 	if (t2 != TOKEN_IDENT && t2 != TOKEN_NUMBER && t2 != TOKEN_SPECIAL)
 		return TOKEN_ERROR;
 
-	strcpy(p, show_token(left));
-	strcat(p, show_token(right));
-	len = strlen(p);
+	{
+		const char *s1 = show_token(left);
+		int l1 = strlen(s1);
+		const char *s2;
+		int l2;
 
-	if (len >= 256)
-		return TOKEN_ERROR;
+		if (l1 > 4096)
+			return TOKEN_ERROR;
+		memcpy(p, s1, l1);
+		s2 = show_token(right);
+		l2 = strlen(s2);
+		if (l1 + l2 > 4096)
+			return TOKEN_ERROR;
+		memcpy(p + l1, s2, l2 + 1);
+		len = l1 + l2;
+	}
 
 	if (t1 == TOKEN_IDENT) {
 		if (t2 == TOKEN_SPECIAL)
@@ -537,7 +562,7 @@ static enum token_type combine(struct token *left, struct token *right, char *p)
 
 static int merge(struct token *left, struct token *right)
 {
-	static char buffer[512];
+	static char buffer[4097];
 	enum token_type res = combine(left, right, buffer);
 	int n;
 
@@ -782,17 +807,22 @@ static int expand(struct token **list, struct symbol *sym)
 
 static const char *token_name_sequence(struct token *token, int endop, struct token *start)
 {
-	static char buffer[256];
+	static char buffer[PATH_MAX];
 	char *ptr = buffer;
 
 	while (!eof_token(token) && !match_op(token, endop)) {
-		int len;
+		size_t len, rem;
 		const char *val;
 		if (token_type(token) == TOKEN_STRING)
 			val = token->string->data;
 		else
 			val = show_token(token);
 		len = strlen(val);
+		rem = sizeof(buffer) - (size_t)(ptr - buffer);
+		if (len >= rem) {
+			sparse_error(token->pos, "header name too long");
+			return NULL;
+		}
 		memcpy(ptr, val, len);
 		ptr += len;
 		token = token->next;
@@ -896,23 +926,30 @@ static void set_stream_include_path(struct stream *stream)
 static int try_include(struct position pos, const char *path, const char *filename, int flen, struct token **where, const char **next_path)
 {
 	int fd;
+	struct stat st;
 	int plen = strlen(path);
-	static char fullname[PATH_MAX];
+	char fullname[PATH_MAX];
 
+	if (!flen || plen + flen + 2 > sizeof(fullname))
+		return 0;
 	memcpy(fullname, path, plen);
 	if (plen && path[plen-1] != '/') {
 		fullname[plen] = '/';
 		plen++;
 	}
 	memcpy(fullname+plen, filename, flen);
+	fullname[plen + flen] = '\0';
 	if (already_tokenized(fullname))
 		return 1;
 	fd = open(fullname, O_RDONLY);
 	if (fd >= 0) {
-		char *streamname = xmemdup(fullname, plen + flen);
-		*where = tokenize(&pos, streamname, fd, *where, next_path);
+		if (fstat(fd, &st) == 0 && !S_ISDIR(st.st_mode)) {
+			char *streamname = xmemdup(fullname, plen + flen + 1);
+			*where = tokenize(&pos, streamname, fd, *where, next_path);
+			close(fd);
+			return 1;
+		}
 		close(fd);
-		return 1;
 	}
 	return 0;
 }
@@ -961,6 +998,8 @@ static int handle_include_path(struct stream *stream, struct token **list, struc
 
 	token = next->next;
 	filename = token_name_sequence(token, expect, token);
+	if (!filename)
+		return 1;
 	flen = strlen(filename) + 1;
 
 	/* Absolute path? */
@@ -1649,6 +1688,14 @@ static int expression_value(struct token **where)
 		list = &p->next;
 	}
 
+	if (state == 1 || state == 2) {
+		sparse_error((*beginning)->pos, "expected identifier after \"defined\"");
+		replace_with_bool(*beginning, 0);
+		(*beginning)->next = &eof_token_entry;
+	} else if (state == 3) {
+		sparse_error((*beginning)->pos, "missing ')' after \"defined\"");
+	}
+
 	p = constant_expression(*where, &expr);
 	if (!eof_token(p))
 		sparse_error(p->pos, "garbage at end: %s", show_token_sequence(p, 0));
diff --git a/validation/preprocessor/preprocess-crash-fixes.c b/validation/preprocessor/preprocess-crash-fixes.c
new file mode 100644
index 000000000000..92ea1ecde717
--- /dev/null
+++ b/validation/preprocessor/preprocess-crash-fixes.c
@@ -0,0 +1,25 @@
+#define REC_A(...) REC_B(__VA_ARGS__)
+#define REC_B(...) REC_A(__VA_ARGS__)
+extern int REC_A(int, ...);
+int rec_nonempty = sizeof(REC_A(1));
+
+/* Recursive macro with empty __VA_ARGS__ must terminate without crash */
+#define REC_E(...) REC_F(__VA_ARGS__)
+#define REC_F(...) REC_E(__VA_ARGS__)
+extern int REC_E(void);
+int rec_empty = sizeof(REC_E());
+
+#define ID(x) x
+int a = ID(1);
+
+/* Octal escape > 0377 */
+int oct_overflow = '\400';
+
+/*
+ * check-name: preprocess-crash-fixes
+ * check-command: sparse -Wno-decl $file
+ *
+ * check-error-start
+preprocessor/preprocess-crash-fixes.c:16:20: warning: octal escape sequence out of range
+ * check-error-end
+ */
diff --git a/validation/preprocessor/preprocess-errors-and-limits.c b/validation/preprocessor/preprocess-errors-and-limits.c
new file mode 100644
index 000000000000..7ad1265ba9c0
--- /dev/null
+++ b/validation/preprocessor/preprocess-errors-and-limits.c
@@ -0,0 +1,33 @@
+#if defined()
+#endif
+#if defined(FOO
+#endif
+#if defined
+#endif
+#if defined(
+#endif
+#include "."
+static const char *s = "\x";
+
+/* 300-character identifier */
+static int aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = 42;
+
+static int get_val(void)
+{
+	return aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa;
+}
+
+/*
+ * check-name: preprocess-errors-and-limits
+ * check-command: sparse $file
+ * check-exit-value: 1
+ *
+ * check-error-start
+preprocessor/preprocess-errors-and-limits.c:10:27: warning: \x used with no following hex digits
+preprocessor/preprocess-errors-and-limits.c:1:13: error: expected preprocessor identifier
+preprocessor/preprocess-errors-and-limits.c:3:13: error: missing ')' after "defined"
+preprocessor/preprocess-errors-and-limits.c:5:5: error: expected identifier after "defined"
+preprocessor/preprocess-errors-and-limits.c:7:5: error: expected identifier after "defined"
+preprocessor/preprocess-errors-and-limits.c:9:10: error: unable to open '.'
+ * check-error-end
+ */

  parent reply	other threads:[~2026-11-09 20:33 UTC|newest]

Thread overview: 74+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-09 20:31 Bart Van Assche
2026-11-09 20:31 ` [PATCH 01/73] Makefile: Pass '--' to rm in clean target to handle dash-prefixed files Bart Van Assche
2026-11-09 20:31 ` [PATCH 02/73] sparse-llvm: Use LLVMCreateBuilderForModule() with LLVM 24 and later Bart Van Assche
2026-11-09 20:31 ` [PATCH 03/73] typeof_unqual: Strip qualifiers and address space from types and arrays Bart Van Assche
2026-11-09 20:31 ` [PATCH 04/73] bitfield: Pack consecutive _Bool bitfields into byte containers Bart Van Assche
2026-11-09 20:31 ` [PATCH 05/73] allocate: Harden slab allocation bounds, zeroing, and mmap checks Bart Van Assche
2026-11-09 20:31 ` [PATCH 06/73] tokenize: Support 4096-byte identifiers and fix escape bounds Bart Van Assche
2026-11-09 20:31 ` Bart Van Assche [this message]
2026-11-09 20:31 ` [PATCH 08/73] options, lib: Harden warning buffers and command-line option parsing Bart Van Assche
2026-11-09 20:31 ` [PATCH 09/73] builtin: Fix atomic builtin type checking and object_size bounds Bart Van Assche
2026-11-09 20:31 ` [PATCH 10/73] parse, show-parse: Fix attribute propagation, enum overflow, and bounds Bart Van Assche
2026-11-09 20:31 ` [PATCH 11/73] target-openrisc: Set target_openrisc.mach to MACH_OPENRISC Bart Van Assche
2026-11-09 20:31 ` [PATCH 12/73] evaluate: Fix pointer arithmetic, incomplete types, and qualifiers Bart Van Assche
2026-11-09 20:31 ` [PATCH 13/73] expand: Fix static initializer folding and floating-point casts Bart Van Assche
2026-11-09 20:32 ` [PATCH 14/73] inline: Copy union cast types and VLA bounds when inlining Bart Van Assche
2026-11-09 20:32 ` [PATCH 15/73] linearize, flow: Fix asm operands, switch case bounds, and DSE Bart Van Assche
2026-11-09 20:32 ` [PATCH 16/73] memops, ssa: Walk parent insns in reverse and skip mixed-size SSA Bart Van Assche
2026-11-09 20:32 ` [PATCH 17/73] simplify: Prevent overflow on LLONG_MIN negation and fix simplifications Bart Van Assche
2026-11-09 20:32 ` [PATCH 18/73] sparse: Gate context failure warning on Wcontext and catch underflow Bart Van Assche
2026-11-09 20:32 ` [PATCH 19/73] ast-model: Return AST_N_COLUMNS and inspect child nodes lazily Bart Van Assche
2026-11-09 20:32 ` [PATCH 20/73] c2xml: Guard against recursive symbols and free xmlGetProp() string Bart Van Assche
2026-11-09 20:32 ` [PATCH 21/73] compile-i386: Fix buffer bounds, unsigned '<=' codegen, and null checks Bart Van Assche
2026-11-09 20:32 ` [PATCH 22/73] ctags: Fix line comparison order and handle SYM_RESTRICT and SYM_LABEL Bart Van Assche
2026-11-09 20:32 ` [PATCH 23/73] dissect: Unwrap array element type via base_type() Bart Van Assche
2026-11-09 20:32 ` [PATCH 24/73] example: Fix show_op() rotating buffer dimensions Bart Van Assche
2026-11-09 20:32 ` [PATCH 25/73] graph: Check PSEUDO_SYM before dereferencing insn->func->sym Bart Van Assche
2026-11-09 20:32 ` [PATCH 26/73] scheck: Fix slice() upper bit index calculation Bart Van Assche
2026-11-09 20:32 ` [PATCH 27/73] sparse-llvm: Support arbitrary struct member counts and fix operand types Bart Van Assche
2026-11-09 20:32 ` [PATCH 28/73] inline: Duplicate generic expressions instead of evaluating them Bart Van Assche
2026-11-09 20:32 ` [PATCH 29/73] lib: Avoid mutating fmt before va_start() in warning() Bart Van Assche
2026-11-09 20:32 ` [PATCH 30/73] cse, canon: Cast pointers to uintptr_t before relational comparison Bart Van Assche
2026-11-09 20:32 ` [PATCH 31/73] context: Add "conditional context increment" model Bart Van Assche
2026-11-09 20:32 ` [PATCH 32/73] expand: Simplify signed constants early Bart Van Assche
2026-11-09 20:32 ` [PATCH 33/73] bitwise: Allow all-bits-set as a restricted value Bart Van Assche
2026-11-09 20:32 ` [PATCH 34/73] bitwise: Allow restricted ordered compares with unrestricted values Bart Van Assche
2026-11-09 20:32 ` [PATCH 35/73] bitwise: Keep unrestricted values unrestricted after a cast Bart Van Assche
2026-11-09 20:32 ` [PATCH 36/73] bitwise: Don't remove MOD_SPECIFIER from 'bitwise' types Bart Van Assche
2026-11-09 20:32 ` [PATCH 37/73] bitwise: Allow both regular casts and forced casts for restricted values Bart Van Assche
2026-11-09 20:32 ` [PATCH 38/73] clang-format: Add .clang-format configuration file Bart Van Assche
2026-11-09 20:32 ` [PATCH 39/73] options: Add support for -std=c23, -std=c2x and -std=gnu23 Bart Van Assche
2026-11-09 20:32 ` [PATCH 40/73] preprocessor: Add support for #elifdef and #elifndef Bart Van Assche
2026-11-09 20:32 ` [PATCH 41/73] preprocessor: Add support for __has_include and __has_include_next Bart Van Assche
2026-11-09 20:32 ` [PATCH 42/73] c23: Add support for static_assert, alignas, alignof, thread_local Bart Van Assche
2026-11-09 20:32 ` [PATCH 43/73] c23: Add support for bool, true, and false keywords Bart Van Assche
2026-11-09 20:32 ` [PATCH 44/73] c23: Add support for nullptr and nullptr_t Bart Van Assche
2026-11-09 20:32 ` [PATCH 45/73] c23: Add support for digit separators in numeric literals Bart Van Assche
2026-11-09 20:32 ` [PATCH 46/73] c23: Support unnamed parameters in function definitions Bart Van Assche
2026-11-09 20:32 ` [PATCH 47/73] c23: Support variadic functions without named parameters Bart Van Assche
2026-11-09 20:32 ` [PATCH 48/73] c23: Support labels before declarations and at block ends Bart Van Assche
2026-11-09 20:32 ` [PATCH 49/73] c23: Support auto type inference Bart Van Assche
2026-11-09 20:32 ` [PATCH 50/73] c23: Support constexpr object specifier Bart Van Assche
2026-11-09 20:32 ` [PATCH 51/73] c23: Support enumerations with fixed underlying types Bart Van Assche
2026-11-09 20:32 ` [PATCH 52/73] c23: Support standard [[]] attribute syntax Bart Van Assche
2026-11-09 20:32 ` [PATCH 53/73] preprocessor: Add support for __has_c_attribute Bart Van Assche
2026-11-09 20:32 ` [PATCH 54/73] c23: Support empty initializer braces {} Bart Van Assche
2026-11-09 20:32 ` [PATCH 55/73] preprocessor: Add support for __has_embed Bart Van Assche
2026-11-09 20:32 ` [PATCH 56/73] preprocessor: Add support for #embed directive Bart Van Assche
2026-11-09 20:32 ` [PATCH 57/73] c23: Add support for u8 character constants and string literals Bart Van Assche
2026-11-09 20:32 ` [PATCH 58/73] preprocessor: Add support for __VA_OPT__ Bart Van Assche
2026-11-09 20:32 ` [PATCH 59/73] c23: Treat empty parameter list () as (void) and reject K&R functions Bart Van Assche
2026-11-09 20:32 ` [PATCH 60/73] c23: Support storage-class specifiers in compound literals Bart Van Assche
2026-11-09 20:32 ` [PATCH 61/73] c23: Add support for _BitInt(N) Bart Van Assche
2026-11-09 20:32 ` [PATCH 62/73] enum: Support machine mode attributes on enumeration types Bart Van Assche
2026-11-09 20:32 ` [PATCH 63/73] enum: Support __attribute__((packed)) " Bart Van Assche
2026-11-09 20:32 ` [PATCH 64/73] expand: Ignore top-level non-array address space in compare_types() Bart Van Assche
2026-11-09 20:32 ` [PATCH 65/73] builtin: Support __builtin_infer_alloc_token() as a size_t ICE Bart Van Assche
2026-11-09 20:32 ` [PATCH 66/73] builtin: Add support for Linux kernel compiler builtins Bart Van Assche
2026-11-09 20:32 ` [PATCH 67/73] builtin: Add support for __builtin_has_attribute() Bart Van Assche
2026-11-09 20:32 ` [PATCH 68/73] cleanup: Support __attribute__((cleanup(...))) in linearize and -Wcontext Bart Van Assche
2026-11-09 20:32 ` [PATCH 69/73] builtin: Do not evaluate __builtin_clz(0) and __builtin_ctz(0) Bart Van Assche
2026-11-09 20:32 ` [PATCH 70/73] pre-process: Support long token and string expansions dynamically Bart Van Assche
2026-11-09 20:32 ` [PATCH 71/73] x86: Undefine conflicting bitness macros when parsing -m flags Bart Van Assche
2026-11-09 20:32 ` [PATCH 72/73] evaluate: Accept conditional expressions with only one void side Bart Van Assche
2026-11-09 20:32 ` [PATCH 73/73] ci: Add Linux kernel GitHub Actions workflow and build script Bart Van Assche

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261109203259.362845-8-bvanassche@acm.org \
    --to=bvanassche@acm.org \
    --cc=linux-sparse@vger.kernel.org \
    --cc=torvalds@linux-foundation.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox