| /*************************************************************************** |
| * _ _ ____ _ |
| * Project ___| | | | _ \| | |
| * / __| | | | |_) | | |
| * | (__| |_| | _ <| |___ |
| * \___|\___/|_| \_\_____| |
| * |
| * Copyright (C) 2019 - 2020, Daniel Stenberg, <[email protected]>, et al. |
| * |
| * This software is licensed as described in the file COPYING, which |
| * you should have received as part of this distribution. The terms |
| * are also available at https://curl.se/docs/copyright.html. |
| * |
| * You may opt to use, copy, modify, merge, publish, distribute and/or sell |
| * copies of the Software, and permit persons to whom the Software is |
| * furnished to do so, under the terms of the COPYING file. |
| * |
| * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
| * KIND, either express or implied. |
| * |
| ***************************************************************************/ |
| #include "curlcheck.h" |
| |
| #include "doh.h" /* from the lib dir */ |
| |
| static CURLcode unit_setup(void) |
| { |
| /* whatever you want done first */ |
| return CURLE_OK; |
| } |
| |
| static void unit_stop(void) |
| { |
| /* done before shutting down and exiting */ |
| } |
| |
| #ifndef CURL_DISABLE_DOH |
| |
| UNITTEST_START |
| |
| /* |
| * Prove detection of write overflow using a short buffer and a name |
| * of maximal valid length. |
| * |
| * Prove detection of other invalid input. |
| */ |
| do { |
| const char *max = |
| /* ..|....1.........2.........3.........4.........5.........6... */ |
| /* 3456789012345678901234567890123456789012345678901234567890123 */ |
| "this.is.a.maximum-length.hostname." /* 34: 34 */ |
| "with-no-label-of-greater-length-than-the-sixty-three-characters." |
| /* 64: 98 */ |
| "specified.in.the.RFCs." /* 22: 120 */ |
| "and.with.a.QNAME.encoding.whose.length.is.exactly." /* 50: 170 */ |
| "the.maximum.length.allowed." /* 27: 197 */ |
| "that.is.two-hundred.and.fifty-six." /* 34: 231 */ |
| "including.the.last.null." /* 24: 255 */ |
| ""; |
| const char *toolong = |
| /* ..|....1.........2.........3.........4.........5.........6... */ |
| /* 3456789012345678901234567890123456789012345678901234567890123 */ |
| "here.is.a.hostname.which.is.just.barely.too.long." /* 49: 49 */ |
| "to.be.encoded.as.a.QNAME.of.the.maximum.allowed.length." |
| /* 55: 104 */ |
| "which.is.256.including.a.final.zero-length.label." /* 49: 153 */ |
| "representing.the.root.node.so.that.a.name.with." /* 47: 200 */ |
| "a.trailing.dot.may.have.up.to." /* 30: 230 */ |
| "255.characters.never.more." /* 26: 256 */ |
| ""; |
| const char *emptylabel = |
| "this.is.an.otherwise-valid.hostname." |
| ".with.an.empty.label."; |
| const char *outsizelabel = |
| "this.is.an.otherwise-valid.hostname." |
| "with-a-label-of-greater-length-than-the-sixty-three-characters-" |
| "specified.in.the.RFCs."; |
| int i; |
| |
| struct test { |
| const char *name; |
| const DOHcode expected_result; |
| }; |
| |
| /* plays the role of struct dnsprobe in urldata.h */ |
| struct demo { |
| unsigned char dohbuffer[255 + 16]; /* deliberately short buffer */ |
| unsigned char canary1; |
| unsigned char canary2; |
| unsigned char canary3; |
| }; |
| |
| const struct test playlist[4] = { |
| { toolong, DOH_DNS_NAME_TOO_LONG }, /* expect early failure */ |
| { emptylabel, DOH_DNS_BAD_LABEL }, /* also */ |
| { outsizelabel, DOH_DNS_BAD_LABEL }, /* also */ |
| { max, DOH_OK } /* expect buffer overwrite */ |
| }; |
| |
| for(i = 0; i < (int)(sizeof(playlist)/sizeof(*playlist)); i++) { |
| const char *name = playlist[i].name; |
| size_t olen = 100000; |
| struct demo victim; |
| DOHcode d; |
| |
| victim.canary1 = 87; /* magic numbers, arbritrarily picked */ |
| victim.canary2 = 35; |
| victim.canary3 = 41; |
| d = doh_encode(name, DNS_TYPE_A, victim.dohbuffer, |
| sizeof(struct demo), /* allow room for overflow */ |
| &olen); |
| |
| fail_unless(d == playlist[i].expected_result, |
| "result returned was not as expected"); |
| if(d == playlist[i].expected_result) { |
| if(name == max) { |
| fail_if(victim.canary1 == 87, |
| "demo one-byte buffer overwrite did not happen"); |
| } |
| else { |
| fail_unless(victim.canary1 == 87, |
| "one-byte buffer overwrite has happened"); |
| } |
| fail_unless(victim.canary2 == 35, |
| "two-byte buffer overwrite has happened"); |
| fail_unless(victim.canary3 == 41, |
| "three-byte buffer overwrite has happened"); |
| } |
| else { |
| if(d == DOH_OK) { |
| fail_unless(olen <= sizeof(victim.dohbuffer), "wrote outside bounds"); |
| fail_unless(olen > strlen(name), "unrealistic low size"); |
| } |
| } |
| } |
| } while(0); |
| |
| /* run normal cases and try to trigger buffer length related errors */ |
| do { |
| DNStype dnstype = DNS_TYPE_A; |
| unsigned char buffer[128]; |
| const size_t buflen = sizeof(buffer); |
| const size_t magic1 = 9765; |
| size_t olen1 = magic1; |
| const char *sunshine1 = "a.com"; |
| const char *dotshine1 = "a.com."; |
| const char *sunshine2 = "aa.com"; |
| size_t olen2; |
| DOHcode ret2; |
| size_t olen; |
| |
| DOHcode ret = doh_encode(sunshine1, dnstype, buffer, buflen, &olen1); |
| fail_unless(ret == DOH_OK, "sunshine case 1 should pass fine"); |
| fail_if(olen1 == magic1, "olen has not been assigned properly"); |
| fail_unless(olen1 > strlen(sunshine1), "bad out length"); |
| |
| /* with a trailing dot, the response should have the same length */ |
| olen2 = magic1; |
| ret2 = doh_encode(dotshine1, dnstype, buffer, buflen, &olen2); |
| fail_unless(ret2 == DOH_OK, "dotshine case should pass fine"); |
| fail_if(olen2 == magic1, "olen has not been assigned properly"); |
| fail_unless(olen1 == olen2, "olen should not grow for a trailing dot"); |
| |
| /* add one letter, the response should be one longer */ |
| olen2 = magic1; |
| ret2 = doh_encode(sunshine2, dnstype, buffer, buflen, &olen2); |
| fail_unless(ret2 == DOH_OK, "sunshine case 2 should pass fine"); |
| fail_if(olen2 == magic1, "olen has not been assigned properly"); |
| fail_unless(olen1 + 1 == olen2, "olen should grow with the hostname"); |
| |
| /* pass a short buffer, should fail */ |
| ret = doh_encode(sunshine1, dnstype, buffer, olen1 - 1, &olen); |
| fail_if(ret == DOH_OK, "short buffer should have been noticed"); |
| |
| /* pass a minimum buffer, should succeed */ |
| ret = doh_encode(sunshine1, dnstype, buffer, olen1, &olen); |
| fail_unless(ret == DOH_OK, "minimal length buffer should be long enough"); |
| fail_unless(olen == olen1, "bad buffer length"); |
| } while(0); |
| UNITTEST_STOP |
| |
| #else /* CURL_DISABLE_DOH */ |
| |
| UNITTEST_START |
| { |
| return 1; /* nothing to do, just fail */ |
| } |
| UNITTEST_STOP |
| |
| #endif |