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
