The %output and %header directives passed the user-supplied file name
straight through to spec_outfile / spec_header_file without rejecting
path separators, so a grammar could steer Bison's generated sources
outside the current directory (e.g. %output "../../x.c"), a path
traversal when Bison is run on untrusted grammars (CVE-2026-56390).

* tests/output.at (%output file name with '/' is rejected)
(%header file name with '/' is rejected): New tests.  Each feeds Bison
a directive whose file name contains '/', and checks that Bison emits
the "invalid %output/%header file name ignored" diagnostic and does not
create the traversal target.  The diagnostic is matched with grep rather
than an exact stderr block so the test does not depend on the source
column reported for the offending token.

AI-Generated: Uses Kiro CLI
Signed-off-by: Pratik Farkase <[email protected]>
---
 tests/output.at | 50 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 50 insertions(+)

diff --git a/tests/output.at b/tests/output.at
index 4ee4bd5..0b9f58f 100644
--- a/tests/output.at
+++ b/tests/output.at
@@ -284,6 +284,56 @@ AT_CHECK_CONFLICTING_OUTPUT([foo.y], [], [-o foo.y],
 ]], 1)
 
 
+## ------------------------------------------- ##
+## Output file names with path separators.     ##
+## ------------------------------------------- ##
+
+# CVE-2026-56390: %output and %header must reject file names containing
+# '/'.  Accepting a grammar-supplied path let a grammar steer Bison's
+# generated sources outside the current directory (path traversal).
+# valid_output_file_name() now rejects such names: Bison emits
+# "invalid %output/%header file name ignored" and does not use the name.
+#
+# The discriminating check is the diagnostic itself: a vulnerable Bison
+# accepts the name and instead tries to open the traversal path, so the
+# "invalid ... file name ignored" grep below fails; a fixed Bison emits
+# it and the grep succeeds.
+#
+# This regression test case was AI-generated (Kiro CLI).
+
+AT_SETUP([%output file name with '/' is rejected])
+
+AT_DATA([input.y],
+[[%output "sub/injected.c"
+%%
+start: %empty;
+]])
+
+AT_BISON_CHECK([input.y], [1], [], [stderr])
+AT_CHECK([grep 'invalid %output file name ignored' stderr], [0], [ignore])
+
+# The traversal target must not have been created.
+AT_CHECK([test ! -e sub/injected.c])
+
+AT_CLEANUP
+
+
+AT_SETUP([%header file name with '/' is rejected])
+
+AT_DATA([input.y],
+[[%header "sub/injected.h"
+%%
+start: %empty;
+]])
+
+AT_BISON_CHECK([input.y], [1], [], [stderr])
+AT_CHECK([grep 'invalid %header file name ignored' stderr], [0], [ignore])
+
+AT_CHECK([test ! -e sub/injected.h])
+
+AT_CLEANUP
+
+
 # AT_CHECK_OUTPUT_FILE_NAME(FILE-NAME-PREFIX, [ADDITIONAL-TESTS])
 # ---------------------------------------------------------------
 m4_define([AT_CHECK_OUTPUT_FILE_NAME],
-- 
2.43.0


Reply via email to