| xf.li | 6c8fc1e | 2023-08-12 00:11:09 -0700 | [diff] [blame] | 1 | /*************************************************************************** | 
 | 2 |  *                                  _   _ ____  _ | 
 | 3 |  *  Project                     ___| | | |  _ \| | | 
 | 4 |  *                             / __| | | | |_) | | | 
 | 5 |  *                            | (__| |_| |  _ <| |___ | 
 | 6 |  *                             \___|\___/|_| \_\_____| | 
 | 7 |  * | 
 | 8 |  * Copyright (C) 2019 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. | 
 | 9 |  * | 
 | 10 |  * This software is licensed as described in the file COPYING, which | 
 | 11 |  * you should have received as part of this distribution. The terms | 
 | 12 |  * are also available at https://curl.se/docs/copyright.html. | 
 | 13 |  * | 
 | 14 |  * You may opt to use, copy, modify, merge, publish, distribute and/or sell | 
 | 15 |  * copies of the Software, and permit persons to whom the Software is | 
 | 16 |  * furnished to do so, under the terms of the COPYING file. | 
 | 17 |  * | 
 | 18 |  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | 
 | 19 |  * KIND, either express or implied. | 
 | 20 |  * | 
 | 21 |  * SPDX-License-Identifier: curl | 
 | 22 |  * | 
 | 23 |  ***************************************************************************/ | 
 | 24 | #include "curlcheck.h" | 
 | 25 |  | 
 | 26 | #include "doh.h" /* from the lib dir */ | 
 | 27 |  | 
 | 28 | static CURLcode unit_setup(void) | 
 | 29 | { | 
 | 30 |   /* whatever you want done first */ | 
 | 31 |   return CURLE_OK; | 
 | 32 | } | 
 | 33 |  | 
 | 34 | static void unit_stop(void) | 
 | 35 | { | 
 | 36 |     /* done before shutting down and exiting */ | 
 | 37 | } | 
 | 38 |  | 
 | 39 | #ifndef CURL_DISABLE_DOH | 
 | 40 |  | 
 | 41 | UNITTEST_START | 
 | 42 |  | 
 | 43 | /* | 
 | 44 |  * Prove detection of write overflow using a short buffer and a name | 
 | 45 |  * of maximal valid length. | 
 | 46 |  * | 
 | 47 |  * Prove detection of other invalid input. | 
 | 48 |  */ | 
 | 49 | do { | 
 | 50 |   static const char max[] = | 
 | 51 |     /* ..|....1.........2.........3.........4.........5.........6... */ | 
 | 52 |     /* 3456789012345678901234567890123456789012345678901234567890123 */ | 
 | 53 |     "this.is.a.maximum-length.hostname."                  /* 34:  34 */ | 
 | 54 |     "with-no-label-of-greater-length-than-the-sixty-three-characters." | 
 | 55 |                                                           /* 64:  98 */ | 
 | 56 |     "specified.in.the.RFCs."                              /* 22: 120 */ | 
 | 57 |     "and.with.a.QNAME.encoding.whose.length.is.exactly."  /* 50: 170 */ | 
 | 58 |     "the.maximum.length.allowed."                         /* 27: 197 */ | 
 | 59 |     "that.is.two-hundred.and.fifty-six."                  /* 34: 231 */ | 
 | 60 |     "including.the.last.null."                            /* 24: 255 */ | 
 | 61 |     ""; | 
 | 62 |   static const char toolong[] = | 
 | 63 |     /* ..|....1.........2.........3.........4.........5.........6... */ | 
 | 64 |     /* 3456789012345678901234567890123456789012345678901234567890123 */ | 
 | 65 |     "here.is.a.hostname.which.is.just.barely.too.long."   /* 49:  49 */ | 
 | 66 |     "to.be.encoded.as.a.QNAME.of.the.maximum.allowed.length." | 
 | 67 |                                                           /* 55: 104 */ | 
 | 68 |     "which.is.256.including.a.final.zero-length.label."   /* 49: 153 */ | 
 | 69 |     "representing.the.root.node.so.that.a.name.with."     /* 47: 200 */ | 
 | 70 |     "a.trailing.dot.may.have.up.to."                      /* 30: 230 */ | 
 | 71 |     "255.characters.never.more."                          /* 26: 256 */ | 
 | 72 |     ""; | 
 | 73 |   static const char emptylabel[] = | 
 | 74 |     "this.is.an.otherwise-valid.hostname." | 
 | 75 |     ".with.an.empty.label."; | 
 | 76 |   static const char outsizelabel[] = | 
 | 77 |     "this.is.an.otherwise-valid.hostname." | 
 | 78 |     "with-a-label-of-greater-length-than-the-sixty-three-characters-" | 
 | 79 |     "specified.in.the.RFCs."; | 
 | 80 |   int i; | 
 | 81 |  | 
 | 82 |   struct test { | 
 | 83 |     const char *name; | 
 | 84 |     const DOHcode expected_result; | 
 | 85 |   }; | 
 | 86 |  | 
 | 87 |   /* plays the role of struct dnsprobe in urldata.h */ | 
 | 88 |   struct demo { | 
 | 89 |     unsigned char dohbuffer[255 + 16]; /* deliberately short buffer */ | 
 | 90 |     unsigned char canary1; | 
 | 91 |     unsigned char canary2; | 
 | 92 |     unsigned char canary3; | 
 | 93 |   }; | 
 | 94 |  | 
 | 95 |   const struct test playlist[4] = { | 
 | 96 |     { toolong, DOH_DNS_NAME_TOO_LONG },  /* expect early failure */ | 
 | 97 |     { emptylabel, DOH_DNS_BAD_LABEL },   /* also */ | 
 | 98 |     { outsizelabel, DOH_DNS_BAD_LABEL }, /* also */ | 
 | 99 |     { max, DOH_OK }                      /* expect buffer overwrite */ | 
 | 100 |   }; | 
 | 101 |  | 
 | 102 |   for(i = 0; i < (int)(sizeof(playlist)/sizeof(*playlist)); i++) { | 
 | 103 |     const char *name = playlist[i].name; | 
 | 104 |     size_t olen = 100000; | 
 | 105 |     struct demo victim; | 
 | 106 |     DOHcode d; | 
 | 107 |  | 
 | 108 |     victim.canary1 = 87; /* magic numbers, arbitrarily picked */ | 
 | 109 |     victim.canary2 = 35; | 
 | 110 |     victim.canary3 = 41; | 
 | 111 |     d = doh_encode(name, DNS_TYPE_A, victim.dohbuffer, | 
 | 112 |                    sizeof(struct demo), /* allow room for overflow */ | 
 | 113 |                    &olen); | 
 | 114 |  | 
 | 115 |     fail_unless(d == playlist[i].expected_result, | 
 | 116 |                 "result returned was not as expected"); | 
 | 117 |     if(d == playlist[i].expected_result) { | 
 | 118 |       if(name == max) { | 
 | 119 |         fail_if(victim.canary1 == 87, | 
 | 120 |                 "demo one-byte buffer overwrite did not happen"); | 
 | 121 |       } | 
 | 122 |       else { | 
 | 123 |         fail_unless(victim.canary1 == 87, | 
 | 124 |                     "one-byte buffer overwrite has happened"); | 
 | 125 |       } | 
 | 126 |       fail_unless(victim.canary2 == 35, | 
 | 127 |                   "two-byte buffer overwrite has happened"); | 
 | 128 |       fail_unless(victim.canary3 == 41, | 
 | 129 |                   "three-byte buffer overwrite has happened"); | 
 | 130 |     } | 
 | 131 |     else { | 
 | 132 |       if(d == DOH_OK) { | 
 | 133 |         fail_unless(olen <= sizeof(victim.dohbuffer), "wrote outside bounds"); | 
 | 134 |         fail_unless(olen > strlen(name), "unrealistic low size"); | 
 | 135 |       } | 
 | 136 |     } | 
 | 137 |   } | 
 | 138 | } while(0); | 
 | 139 |  | 
 | 140 | /* run normal cases and try to trigger buffer length related errors */ | 
 | 141 | do { | 
 | 142 |   DNStype dnstype = DNS_TYPE_A; | 
 | 143 |   unsigned char buffer[128]; | 
 | 144 |   const size_t buflen = sizeof(buffer); | 
 | 145 |   const size_t magic1 = 9765; | 
 | 146 |   size_t olen1 = magic1; | 
 | 147 |   const char *sunshine1 = "a.com"; | 
 | 148 |   const char *dotshine1 = "a.com."; | 
 | 149 |   const char *sunshine2 = "aa.com"; | 
 | 150 |   size_t olen2; | 
 | 151 |   DOHcode ret2; | 
 | 152 |   size_t olen; | 
 | 153 |  | 
 | 154 |   DOHcode ret = doh_encode(sunshine1, dnstype, buffer, buflen, &olen1); | 
 | 155 |   fail_unless(ret == DOH_OK, "sunshine case 1 should pass fine"); | 
 | 156 |   fail_if(olen1 == magic1, "olen has not been assigned properly"); | 
 | 157 |   fail_unless(olen1 > strlen(sunshine1), "bad out length"); | 
 | 158 |  | 
 | 159 |   /* with a trailing dot, the response should have the same length */ | 
 | 160 |   olen2 = magic1; | 
 | 161 |   ret2 = doh_encode(dotshine1, dnstype, buffer, buflen, &olen2); | 
 | 162 |   fail_unless(ret2 == DOH_OK, "dotshine case should pass fine"); | 
 | 163 |   fail_if(olen2 == magic1, "olen has not been assigned properly"); | 
 | 164 |   fail_unless(olen1 == olen2, "olen should not grow for a trailing dot"); | 
 | 165 |  | 
 | 166 |   /* add one letter, the response should be one longer */ | 
 | 167 |   olen2 = magic1; | 
 | 168 |   ret2 = doh_encode(sunshine2, dnstype, buffer, buflen, &olen2); | 
 | 169 |   fail_unless(ret2 == DOH_OK, "sunshine case 2 should pass fine"); | 
 | 170 |   fail_if(olen2 == magic1, "olen has not been assigned properly"); | 
 | 171 |   fail_unless(olen1 + 1 == olen2, "olen should grow with the hostname"); | 
 | 172 |  | 
 | 173 |   /* pass a short buffer, should fail */ | 
 | 174 |   ret = doh_encode(sunshine1, dnstype, buffer, olen1 - 1, &olen); | 
 | 175 |   fail_if(ret == DOH_OK, "short buffer should have been noticed"); | 
 | 176 |  | 
 | 177 |   /* pass a minimum buffer, should succeed */ | 
 | 178 |   ret = doh_encode(sunshine1, dnstype, buffer, olen1, &olen); | 
 | 179 |   fail_unless(ret == DOH_OK, "minimal length buffer should be long enough"); | 
 | 180 |   fail_unless(olen == olen1, "bad buffer length"); | 
 | 181 | } while(0); | 
 | 182 | UNITTEST_STOP | 
 | 183 |  | 
 | 184 | #else /* CURL_DISABLE_DOH */ | 
 | 185 |  | 
 | 186 | UNITTEST_START | 
 | 187 | { | 
 | 188 |   return 1; /* nothing to do, just fail */ | 
 | 189 | } | 
 | 190 | UNITTEST_STOP | 
 | 191 |  | 
 | 192 | #endif |