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