33#include <assert.h>
44#include <inttypes.h>
55#include <stdlib.h>
6+ #include <errno.h>
7+ #include <stdio.h>
68
79struct lexbuf {
810 uint8_t * untrusted_cursor ;
11+ uint8_t * outbuf ;
12+ const uint8_t * const end ;
913};
1014
15+ static void buf_assert_invariants (const struct lexbuf * const buf )
16+ {
17+ assert (buf != NULL );
18+ assert (buf -> untrusted_cursor != NULL );
19+ assert (buf -> outbuf != NULL );
20+ assert (buf -> end != NULL );
21+ assert (buf -> outbuf <= buf -> untrusted_cursor );
22+ assert (buf -> untrusted_cursor <= buf -> end );
23+ assert (* buf -> end == '\0' );
24+ }
25+
1126static bool buf_consume (struct lexbuf * const buf , uint8_t const expected )
1227{
28+ buf_assert_invariants (buf );
29+ assert (expected != '\0' );
1330 bool ret = buf -> untrusted_cursor [0 ] == expected ;
1431 buf -> untrusted_cursor += ret ;
1532 return ret ;
1633}
1734
35+ /*
36+ * Add this amount of bytes to the buffer.
37+ * Advances the output cursor.
38+ * Returns pointer to the start of the data.
39+ * Pass NULL to shift data in-place and advance the input cursor too.
40+ */
41+ static uint8_t * buf_put (struct lexbuf * buf , const uint8_t * ptr , size_t size )
42+ {
43+ buf_assert_invariants (buf );
44+ uint8_t * const outbuf = buf -> outbuf ;
45+ if (size != 0 ) {
46+ if (ptr != NULL ) {
47+ assert ((size_t )(buf -> untrusted_cursor - outbuf ) >= size );
48+ memmove (outbuf , ptr , size );
49+ } else {
50+ assert ((size_t )(buf -> end - buf -> untrusted_cursor ) >= size );
51+ memmove (outbuf , buf -> untrusted_cursor , size );
52+ buf -> untrusted_cursor += size ;
53+ }
54+ buf -> outbuf = outbuf + size ;
55+ }
56+ return outbuf ;
57+ }
58+
59+ static void buf_putc (struct lexbuf * buf , uint8_t c )
60+ {
61+ buf_assert_invariants (buf );
62+ assert (buf -> outbuf < buf -> untrusted_cursor );
63+ * buf -> outbuf ++ = c ;
64+ }
65+
1866static bool is_delim (uint8_t const untrusted_c )
1967{
2068 return untrusted_c == ' ' || untrusted_c == ',' ;
@@ -23,27 +71,39 @@ static bool is_delim(uint8_t const untrusted_c)
2371/* Skip spaces and commas */
2472static void buf_consume_delims (struct lexbuf * const buf )
2573{
74+ buf_assert_invariants (buf );
2675 while (is_delim (buf -> untrusted_cursor [0 ]))
2776 buf -> untrusted_cursor ++ ;
2877}
2978
3079static void consume_subpacket_number (struct lexbuf * const buf )
3180{
81+ buf_assert_invariants (buf );
3282 char * endptr = NULL ;
33- long untrusted_subpacket_number = strtol ((const char * )buf -> untrusted_cursor , & endptr , 10 );
83+ errno = 0 ;
84+ const uint8_t * const last = buf -> untrusted_cursor ;
85+ long untrusted_subpacket_number = strtol ((const char * )last , & endptr , 10 );
3486 if (endptr == NULL )
3587 abort ();
36- if ((const uint8_t * )endptr == buf -> untrusted_cursor )
88+ // Safety: strtol() guarantees that this is not past the NUL terminator.
89+ buf -> untrusted_cursor = (uint8_t * )endptr ;
90+ if ((const uint8_t * )endptr == last )
3791 errx (1 , "Invalid character in subpacket number list" );
38- if (untrusted_subpacket_number < 1 || untrusted_subpacket_number > 127 )
92+ if (errno || untrusted_subpacket_number < 1 || untrusted_subpacket_number > 127 )
3993 errx (1 , "Subpacket number not valid (must be between 1 and 127 inclusive, got %ld)" ,
4094 untrusted_subpacket_number );
95+ char outbuf [4 ];
96+ int r = snprintf (outbuf , sizeof (outbuf ), "%ld" , untrusted_subpacket_number );
97+ if (r < 1 || r > 3 )
98+ errx (1 , "snprintf failed" );
4199 switch (* endptr ) {
42100 case '"' :
43101 case ',' :
44102 case ' ' :
45103 case '\0' :
46- buf -> untrusted_cursor = (uint8_t * )endptr ;
104+ // This cannot fail an assertion because snprintf()
105+ // always uses the fewest bytes possible.
106+ buf_put (buf , (const uint8_t * )outbuf , (size_t )r );
47107 return ;
48108 default :
49109 errx (1 , "Invalid character %c following subpacket number" , * endptr );
@@ -53,13 +113,21 @@ static void consume_subpacket_number(struct lexbuf *const buf)
53113static void consume_subpackets_argument (struct lexbuf * const buf )
54114{
55115 if (buf_consume (buf , '"' )) {
116+ buf_putc (buf , '"' );
56117 if (!strchr ((const char * )buf -> untrusted_cursor , '"' ))
57118 errx (1 , "Unterminated quoted option argument" );
58119 buf_consume_delims (buf );
120+ bool seen_subpacket_number = false;
59121 while (!buf_consume (buf , '"' )) {
122+ if (* buf -> untrusted_cursor == '\0' )
123+ errx (1 , "Unterminated quoted option argument" );
124+ if (seen_subpacket_number )
125+ buf_putc (buf , ',' );
126+ seen_subpacket_number = true;
60127 consume_subpacket_number (buf );
61128 buf_consume_delims (buf );
62129 }
130+ buf_putc (buf , '"' );
63131 } else {
64132 consume_subpacket_number (buf );
65133 }
@@ -97,13 +165,10 @@ static const struct listopt *find_option(const uint8_t *const untrusted_option,
97165 return options ;
98166}
99167
100- void sanitize_option_list (const struct listopt * allowed_list_options , const char * const msg )
168+ static void fixup_option_list (const struct listopt * allowed_list_options ,
169+ const char * const msg ,
170+ struct lexbuf * const buf )
101171{
102- if (!strcmp (optarg , "help" ))
103- return ; // allow --list-options=help
104- struct lexbuf buf_ = { .untrusted_cursor = (uint8_t * )optarg };
105- struct lexbuf * const buf = & buf_ ;
106-
107172 for (const char * untrusted_c = optarg ; * untrusted_c ; untrusted_c ++ ) {
108173 /* char is signed on x86, so the first check is not redundant */
109174 if (* untrusted_c < 0x20 || * untrusted_c > 0x7E )
@@ -113,14 +178,34 @@ void sanitize_option_list(const struct listopt *allowed_list_options, const char
113178 for (;;) {
114179 /* Skip leading spaces and commas */
115180 buf_consume_delims (buf );
116- if (buf -> untrusted_cursor [0 ] == '\0' )
117- return ; /* Nothing to do */
181+ if (buf -> untrusted_cursor == buf -> end ) {
182+ /* Avoid failing an assertion if nothing was stripped. */
183+ if (buf -> untrusted_cursor != buf -> outbuf )
184+ buf_putc (buf , '\0' );
185+ break ; /* Nothing to do */
186+ }
187+
188+ if (buf -> outbuf != (const uint8_t * )optarg ) {
189+ /*
190+ * Add a comma to delimit options.
191+ * Proof that this will not assert:
192+ *
193+ * - Options must be followed by '\0', '=', ',' or ' '.
194+ * - If the option is followed by '\0' the loop exits above.
195+ * - If the option is followed by ' ' or ',', the above buf_consume_delims() call
196+ * advances the cursor, unless it has already been advanced below.
197+ * - If the option is followed by '=', it is required to be followed by ' ' or ','
198+ * after the option argument. buf_consume_delims() will consume it.
199+ */
200+ buf_putc (buf , ',' );
201+ }
118202
119203 /* Read the identifier */
120- uint8_t * const untrusted_option = buf -> untrusted_cursor ;
121- size_t const option_len = read_option (untrusted_option );
204+ size_t const option_len = read_option (buf -> untrusted_cursor );
122205 if (option_len == 0 )
123206 errx (1 , "'=' not following a %s option" , msg );
207+ /* Slide the option back. */
208+ uint8_t * untrusted_option = buf_put (buf , NULL , option_len );
124209
125210 bool const negated = option_len >= 3 &&
126211 memcmp (untrusted_option , "no-" , 3 ) == 0 ;
@@ -134,8 +219,6 @@ void sanitize_option_list(const struct listopt *allowed_list_options, const char
134219 errx (1 , "Forbidden %s option %.*s" , msg ,
135220 (int )option_len , untrusted_option );
136221
137- buf -> untrusted_cursor += option_len ;
138-
139222 while (buf_consume (buf , ' ' )) {}
140223
141224 if (!buf_consume (buf , '=' ))
@@ -146,6 +229,8 @@ void sanitize_option_list(const struct listopt *allowed_list_options, const char
146229 if (negated || !p -> has_argument )
147230 errx (1 , "%s option %.*s does not take an argument" , msg ,
148231 (int )option_len , untrusted_option );
232+ /* just consumed an '=' so this is okay */
233+ buf_putc (buf , '=' );
149234
150235 if (buf -> untrusted_cursor [0 ] && buf -> untrusted_cursor [0 ] != ',' )
151236 consume_subpackets_argument (buf );
@@ -155,3 +240,17 @@ void sanitize_option_list(const struct listopt *allowed_list_options, const char
155240 "(found %c)" , msg , buf -> untrusted_cursor [0 ]);
156241 }
157242}
243+
244+ void sanitize_option_list (const struct listopt * allowed_list_options , const char * const msg )
245+ {
246+ if (!strcmp (optarg , "help" ))
247+ return ; // allow --list-options=help
248+ struct lexbuf buf = {
249+ .untrusted_cursor = (uint8_t * )optarg ,
250+ .outbuf = (uint8_t * )optarg ,
251+ .end = (const uint8_t * )optarg + strlen (optarg ),
252+ };
253+ fixup_option_list (allowed_list_options , msg , & buf );
254+ /* NUL terminate the buffer */
255+ memset (buf .outbuf , 0 , (size_t )(buf .end - buf .outbuf ));
256+ }
0 commit comments