1 /***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 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
unit_setup(void)28 static CURLcode unit_setup(void)
29 {
30 /* whatever you want done first */
31 return CURLE_OK;
32 }
33
unit_stop(void)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_req_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_req_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_req_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_req_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_req_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_req_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 /* nothing to do, just succeed */
188 UNITTEST_STOP
189
190 #endif
191