[v2 PATCH] builtin: Fix octal escapes in dollar-single-quotes

Herbert Xu <[email protected]> Sun, 31 May 2026 13:36:09 +0800
Newsgroups org.kernel.vger.dash
Message-ID <[email protected]>
Kerin Millar <[email protected]> wrote:
> 
> The test suite of the gentoo-functions project recently uncovered a bug whereby octal sequences within dollar-single-quotes are evaluated as internal control characters, resulting in faulty decoding at best, and segmentation faults at worse. The attached patch rectifies this bug.

Thanks for the patch.

While looking at this I found out that the logic for \x is broken
as it always produces a multi-byte character even for the value of
\x81.

So here is a rolled-up patch which fixes both problems.

---8<---
From: Kerin Millar <[email protected]>

The test suite of gentoo-functions recently uncovered a bug concerning
the handling of octal escape sequences within dollar-single-quotes.
Here is a reproducer:

    $ dash -c "x=\$'\\201'; printf '%s' \"\$x\"" | od -An -tx1
    88

That is, despite the input being 0x81, the output is 0x88. Indeed, any
input between 0x81..0x88 is adversely affected and some - such as 0x82 -
induce a segfault.

I noticed the following macros in src/parser.h:

    #define CTL_FIRST -127 /* first 'special' character */
    #define CTLESC    -127 /* escape next character */
    #define CTL_LAST  -120 /* last 'special' character */

Reinterpreted as unsigned char, the CTL_FIRST..CTL_LAST range maps
exactly to 0x81..0x88:

    $ perl -e 'printf "%x\0", $_ & 0xFF for -127..-120' | xargs -0
    81 82 83 84 85 86 87 88

From this, I was able to deduce that all of these bytes must be preceded
by CTLESC in order to be taken literally. Make it so.

Link: https://gitweb.gentoo.org/proj/gentoo-functions.git/commit/?id=947090fb7704
Signed-off-by: Kerin Millar <[email protected]>

The escape should also be applied to \x sequences.  In fact \x81
was totally broken as it produced a multi-byte character.  Fix this
by merging these two code paths.

Signed-off-by: Herbert Xu <[email protected]>

diff --git a/src/bltin/printf.c b/src/bltin/printf.c
index 106aecd..671e781 100644
--- a/src/bltin/printf.c
+++ b/src/bltin/printf.c
@@ -332,6 +332,7 @@ unsigned conv_escape(char *str0, char *out0, bool mbchar)
 	char *out = out0;
 	char *str = str0;
 	unsigned value;
+	int och;
 	int ch;
 
 	ch = *str;
@@ -359,12 +360,18 @@ unsigned conv_escape(char *str0, char *out0, bool mbchar)
 		}
 
 		str--;
+
+check_value:
+		if (mbchar && (signed char)value >= CTL_FIRST &&
+		    (signed char)value <= CTL_LAST)
+			USTPUTC(CTLESC, out);
 		break;
 
 	case 'x':
 		ch = 2;
 
 hex:
+		och = ch;
 		value = 0;
 		do {
 			int c = *++str;
@@ -391,6 +398,9 @@ hex:
 		if (value < 0x80)
 			break;
 
+		if (och <= 2)
+			goto check_value;
+
 		if (value < 0x110000) {
 			int mboff = (mbchar - 1) * 2;
 			unsigned uni = value;
-- 
Email: Herbert Xu <[email protected]>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt