]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Handle NUL in text files better
authorMark Andrews <marka@isc.org>
Wed, 11 Mar 2026 22:23:00 +0000 (09:23 +1100)
committerMark Andrews <marka@isc.org>
Tue, 11 Aug 2026 07:16:32 +0000 (17:16 +1000)
NULs in text files indicate corruption.  Additionally embedded NULs
can cause text files to be interpreted differently to the way they
appear to be when read by humans.  NUL in text files now returns
token type isc_tokentype_unknown.

fuzz/dns_rdata_fromwire_text.c
lib/dns/master.c
lib/isc/include/isc/lex.h
lib/isc/lex.c
tests/dns/skr_test.c
tests/isc/lex_test.c
tests/libtest/dns.c

index 0e0f2447664ab4a568074710723b2b2d98873cdb..98ff4d46209251cf9c5cab43300e2796ac4fc2b7 100644 (file)
@@ -49,7 +49,6 @@ LLVMFuzzerInitialize(int *argc ISC_ATTR_UNUSED, char ***argv ISC_ATTR_UNUSED) {
        isc_lex_create(mctx, 64, &lex);
 
        memset(specials, 0, sizeof(specials));
-       specials[0] = 1;
        specials['('] = 1;
        specials[')'] = 1;
        specials['"'] = 1;
index b70fb4d99d3a27813c04b5c4ac112ddca1e4fe00..c43a47d04c0821c9d36d6f1b918bb59944192af1 100644 (file)
@@ -534,7 +534,6 @@ loadctx_create(dns_masterformat_t format, isc_mem_t *mctx, unsigned int options,
                 * in lib/dns/tests/dnstest.c.
                 */
                memset(specials, 0, sizeof(specials));
-               specials[0] = 1;
                specials['('] = 1;
                specials[')'] = 1;
                specials['"'] = 1;
index a4faf25667a8a17cd108d3fbe054195db74b22b5..684d8b416df942be487cbc20b9b9e1e0c118a9ee 100644 (file)
@@ -201,7 +201,7 @@ void
 isc_lex_setspecials(isc_lex_t *lex, isc_lexspecials_t specials);
 /*!<
  * The characters in 'specials' are returned as tokens.  Along with
- * whitespace, they delimit strings and numbers.
+ * whitespace and NUL, they delimit strings and numbers.
  *
  * Note:
  *\li  Comment processing takes precedence over special character
index 588ffa6bf1ef525ec0c9ba8e62806b04a0fb3949..db7bc85d3ec682b1a8a6741dd96516ecf882cc6b 100644 (file)
@@ -540,6 +540,10 @@ isc_lex_gettoken(isc_lex_t *lex, unsigned int options, isc_token_t *tokenp) {
                                lex->last_was_eol = false;
                                no_comments = true;
                                state = lexstate_qstring;
+                       } else if (c == '\0') {
+                               tokenp->type = isc_tokentype_unknown;
+                               tokenp->value.as_char = c;
+                               done = true;
                        } else if (lex->specials[c]) {
                                lex->last_was_eol = false;
                                if ((c == '(' || c == ')') &&
@@ -607,7 +611,8 @@ isc_lex_gettoken(isc_lex_t *lex, unsigned int options, isc_token_t *tokenp) {
                case lexstate_number:
                        if (c == EOF || !isdigit((unsigned char)c)) {
                                if (c == ' ' || c == '\t' || c == '\r' ||
-                                   c == '\n' || c == EOF || lex->specials[c])
+                                   c == '\n' || c == '\0' || c == EOF ||
+                                   lex->specials[c])
                                {
                                        int base;
                                        if ((options & ISC_LEXOPT_OCTAL) != 0) {
@@ -700,8 +705,8 @@ isc_lex_gettoken(isc_lex_t *lex, unsigned int options, isc_token_t *tokenp) {
                         * as lex->specials[EOF] is not a good idea.
                         */
                        if (c == '\r' || c == '\n' || c == EOF ||
-                           (!escaped &&
-                            (c == ' ' || c == '\t' || lex->specials[c])))
+                           (!escaped && (c == ' ' || c == '\t' || c == '\0' ||
+                                         lex->specials[c])))
                        {
                                pushback(source, c);
                                if (source->result != ISC_R_SUCCESS) {
index 8f29f304f314b9b872388ac24363ac8da7456120..54e9b1311d6a0d352093251be289324e7aaf28ca 100644 (file)
@@ -167,7 +167,6 @@ write_record(FILE *fp, dns_rdatatype_t rdtype, const char *rdatastr,
 
        /* Create a lexer as one is required by dns_rdata_fromtext(). */
        isc_lex_create(isc_g_mctx, 64, &lex);
-       specials[0] = 1;
        specials['('] = 1;
        specials[')'] = 1;
        specials['"'] = 1;
index 8db4d60944493325968374c712c951d829193318..764a8102709ea3a7065fca2c472bd457b901f693 100644 (file)
 
 #define AS_STR(x) (x).value.as_textregion.base
 
+/* check handling of 0x00 */
+ISC_RUN_TEST_IMPL(lex_0x00) {
+       isc_result_t result;
+       isc_lex_t *lex = NULL;
+       isc_buffer_t buf;
+       isc_token_t token;
+
+       unsigned char nul_then_A[] = { '\0', 'A' };
+       unsigned char embedded_null[] = { '"', 'a', '\0', 'b', '"' };
+       unsigned char escaped_null[] = { 'a', '\\', '\0', 'b' };
+
+       UNUSED(state);
+
+       isc_lex_create(isc_g_mctx, 1024, &lex);
+
+       isc_buffer_init(&buf, &nul_then_A[0], sizeof(nul_then_A));
+       isc_buffer_add(&buf, sizeof(nul_then_A));
+
+       result = isc_lex_openbuffer(lex, &buf);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       result = isc_lex_gettoken(lex, 0, &token);
+       assert_int_equal(result, ISC_R_SUCCESS);
+       assert_int_equal(token.type, isc_tokentype_unknown);
+
+       result = isc_lex_gettoken(lex, 0, &token);
+       assert_int_equal(result, ISC_R_SUCCESS);
+       assert_int_equal(token.type, isc_tokentype_string);
+
+       isc_lex_close(lex);
+
+       /*
+        * Check that an embedded NUL is preserved in a quoted string.
+        */
+       isc_buffer_init(&buf, &embedded_null[0], sizeof(embedded_null));
+       isc_buffer_add(&buf, sizeof(embedded_null));
+
+       result = isc_lex_openbuffer(lex, &buf);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       result = isc_lex_gettoken(lex, ISC_LEXOPT_QSTRING, &token);
+       assert_int_equal(result, ISC_R_SUCCESS);
+       assert_int_equal(token.type, isc_tokentype_qstring);
+       assert_int_equal(token.value.as_textregion.length, 3);
+       assert_memory_equal(token.value.as_textregion.base, "a\0b", 3);
+
+       isc_lex_close(lex);
+
+       /*
+        * Check that an escaped NUL is preserved.
+        */
+       isc_buffer_init(&buf, &escaped_null[0], sizeof(escaped_null));
+       isc_buffer_add(&buf, sizeof(escaped_null));
+
+       result = isc_lex_openbuffer(lex, &buf);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       result = isc_lex_gettoken(lex, ISC_LEXOPT_ESCAPE, &token);
+       assert_int_equal(result, ISC_R_SUCCESS);
+       assert_int_equal(token.type, isc_tokentype_string);
+       assert_int_equal(token.value.as_textregion.length, 4);
+       assert_memory_equal(token.value.as_textregion.base, "a\\\0b", 4);
+
+       isc_lex_destroy(&lex);
+}
+
 /* check handling of 0xff */
 ISC_RUN_TEST_IMPL(lex_0xff) {
        isc_result_t result;
@@ -72,8 +138,8 @@ ISC_RUN_TEST_IMPL(lex_setline) {
 
        isc_lex_create(isc_g_mctx, 1024, &lex);
 
-       isc_buffer_init(&buf, &text[0], sizeof(text));
-       isc_buffer_add(&buf, sizeof(text));
+       isc_buffer_init(&buf, &text[0], sizeof(text) - 1);
+       isc_buffer_add(&buf, sizeof(text) - 1);
 
        result = isc_lex_openbuffer(lex, &buf);
        assert_int_equal(result, ISC_R_SUCCESS);
@@ -340,6 +406,7 @@ ISC_RUN_TEST_IMPL(lex_keypair) {
 }
 
 ISC_TEST_LIST_START
+ISC_TEST_ENTRY(lex_0x00)
 ISC_TEST_ENTRY(lex_0xff)
 ISC_TEST_ENTRY(lex_keypair)
 ISC_TEST_ENTRY(lex_setline)
index 9b6b8dc655dcfab2e9ff07a93b455f97e9ab5d56..e98b0e15b18714281124bae0214ccdd1e62bcae9 100644 (file)
@@ -350,7 +350,6 @@ dns_test_rdatafromstring(dns_rdata_t *rdata, dns_rdataclass_t rdclass,
         * delimiters while reading the source string.  These should match
         * specials from lib/dns/master.c.
         */
-       specials[0] = 1;
        specials['('] = 1;
        specials[')'] = 1;
        specials['"'] = 1;