From 4eda297bbd2128f2e53585f18b9fbb5cc5eb0605 Mon Sep 17 00:00:00 2001 From: Alex Rousskov Date: Wed, 22 Apr 2026 11:35:43 -0400 Subject: [PATCH 1/2] Fix handling of truncated legacy errorpage %codes noteBuildError_: ... Unsupported error page %code near % When build.input ends with a bare percent character, we must only copy/consume that character and increment build.input by 1, not 2. This overread bug existed since errorpage templates were introduced in 1997 commit 9b312a19. 2010 commit 4d16918e significantly broadened the kinds of use cases that can trigger this bug. --- src/errorpage.cc | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/errorpage.cc b/src/errorpage.cc index f999c114a9e..9396f4cfa77 100644 --- a/src/errorpage.cc +++ b/src/errorpage.cc @@ -958,7 +958,8 @@ ErrorState::compileLegacyCode(Build &build) const auto &building_deny_info_url = build.building_deny_info_url; // a change reduction hack - const auto letter = build.input[1]; + Assure(*build.input == '%'); + const auto letter = build.input[1]; // may be the terminating NUL switch (letter) { @@ -1261,6 +1262,17 @@ ErrorState::compileLegacyCode(Build &build) p = "%"; break; + case '\0': + // XXX: Partially duplicates error handling code of the `default:` case. + // TODO: Refactor bypassBuildErrorXXX() to accept `build` and determine the source of the error. + if (building_deny_info_url) + bypassBuildErrorXXX("Bare % at the end of deny_info", build.input); + else + bypassBuildErrorXXX("Bare % at the end of error page", build.input); + p = "%"; + do_quote = 0; + break; + default: if (building_deny_info_url) bypassBuildErrorXXX("Unsupported deny_info %code", build.input); @@ -1268,6 +1280,7 @@ ErrorState::compileLegacyCode(Build &build) bypassBuildErrorXXX("Unsupported error page %code", build.input); // else too many "font-size: 100%;" template errors to report + Assure(build.input[1]); mb.append(build.input, 2); do_quote = 0; break; @@ -1288,7 +1301,9 @@ ErrorState::compileLegacyCode(Build &build) // TODO: Optimize by replacing mb with direct build.output usage. build.output.append(p, strlen(p)); - build.input += 2; + ++build.input; // skip the parsed % character + if (letter) + ++build.input; // when it was present, skip the parsed letter after % } void From 9c17b95e2a107d05a90a98d51ec676f917b08002 Mon Sep 17 00:00:00 2001 From: Alex Rousskov Date: Wed, 22 Apr 2026 15:29:47 -0400 Subject: [PATCH 2/2] Do not dump a NUL character to the debugging stream errorpage.cc(1294) compileLegacyCode: %? --> '%' Nothing was printed after the first '%' char... --- src/errorpage.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/errorpage.cc b/src/errorpage.cc index 9396f4cfa77..2569d3b3ca5 100644 --- a/src/errorpage.cc +++ b/src/errorpage.cc @@ -1291,7 +1291,8 @@ ErrorState::compileLegacyCode(Build &build) assert(p); - debugs(4, 3, "%" << letter << " --> '" << p << "'" ); + // TODO: Add an I/O manipulator to report non-printable chars better. + debugs(4, 3, "%" << (letter ? letter : '?') << " --> '" << p << "'" ); if (do_quote) p = html_quote(p);