1 /* $NetBSD: option_unittest.c,v 1.5 2022/10/05 22:20:15 christos Exp $ */
2
3 /*
4 * Copyright (C) 2018-2022 Internet Systems Consortium, Inc. ("ISC")
5 *
6 * This Source Code Form is subject to the terms of the Mozilla Public
7 * License, v. 2.0. If a copy of the MPL was not distributed with this
8 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
11 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
12 * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
13 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
14 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
15 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
16 * PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 #include <config.h>
20 #include <atf-c.h>
21 #include "dhcpd.h"
22
23 ATF_TC(option_refcnt);
24
ATF_TC_HEAD(option_refcnt,tc)25 ATF_TC_HEAD(option_refcnt, tc)
26 {
27 atf_tc_set_md_var(tc, "descr",
28 "Verify option reference count does not overflow.");
29 }
30
31 /* This test does a simple check to see if option reference count is
32 * decremented even an error path exiting parse_option_buffer()
33 */
ATF_TC_BODY(option_refcnt,tc)34 ATF_TC_BODY(option_refcnt, tc)
35 {
36 struct option_state *options;
37 struct option *option;
38 unsigned code;
39 int refcnt;
40 unsigned char buffer[3] = { 15, 255, 0 };
41
42 initialize_common_option_spaces();
43
44 options = NULL;
45 if (!option_state_allocate(&options, MDL)) {
46 atf_tc_fail("can't allocate option state");
47 }
48
49 option = NULL;
50 code = 15; /* domain-name */
51 if (!option_code_hash_lookup(&option, dhcp_universe.code_hash,
52 &code, 0, MDL)) {
53 atf_tc_fail("can't find option 15");
54 }
55 if (option == NULL) {
56 atf_tc_fail("option is NULL");
57 }
58 refcnt = option->refcnt;
59
60 buffer[0] = 15;
61 buffer[1] = 255; /* invalid */
62 buffer[2] = 0;
63
64 if (parse_option_buffer(options, buffer, 3, &dhcp_universe)) {
65 atf_tc_fail("parse_option_buffer is expected to fail");
66 }
67
68 if (refcnt != option->refcnt) {
69 atf_tc_fail("refcnt changed from %d to %d", refcnt, option->refcnt);
70 }
71 }
72
73 ATF_TC(pretty_print_option);
74
ATF_TC_HEAD(pretty_print_option,tc)75 ATF_TC_HEAD(pretty_print_option, tc)
76 {
77 atf_tc_set_md_var(tc, "descr",
78 "Verify pretty_print_option does not overrun its buffer.");
79 }
80
81
82 /*
83 * This test verifies that pretty_print_option() will not overrun its
84 * internal, static buffer when given large 'x/X' format options.
85 *
86 */
ATF_TC_BODY(pretty_print_option,tc)87 ATF_TC_BODY(pretty_print_option, tc)
88 {
89 struct option *option;
90 unsigned code;
91 unsigned char bad_data[32*1024];
92 unsigned char good_data[] = { 1,2,3,4,5,6 };
93 int emit_commas = 1;
94 int emit_quotes = 1;
95 const char *output_buf;
96
97 /* Initialize whole thing to non-printable chars */
98 memset(bad_data, 0x1f, sizeof(bad_data));
99
100 initialize_common_option_spaces();
101
102 /* We'll use dhcp_client_identitifer because it happens to be format X */
103 code = 61;
104 option = NULL;
105 if (!option_code_hash_lookup(&option, dhcp_universe.code_hash,
106 &code, 0, MDL)) {
107 atf_tc_fail("can't find option %d", code);
108 }
109
110 if (option == NULL) {
111 atf_tc_fail("option is NULL");
112 }
113
114 /* First we will try a good value we know should fit. */
115 output_buf = pretty_print_option (option, good_data, sizeof(good_data),
116 emit_commas, emit_quotes);
117
118 /* Make sure we get what we expect */
119 if (!output_buf || strcmp(output_buf, "1:2:3:4:5:6")) {
120 atf_tc_fail("pretty_print_option did not return \"<error>\"");
121 }
122
123
124 /* Now we'll try a data value that's too large */
125 output_buf = pretty_print_option (option, bad_data, sizeof(bad_data),
126 emit_commas, emit_quotes);
127
128 /* Make sure we safely get an error */
129 if (!output_buf || strcmp(output_buf, "<error>")) {
130 atf_tc_fail("pretty_print_option did not return \"<error>\"");
131 }
132 }
133
134 ATF_TC(parse_X);
135
ATF_TC_HEAD(parse_X,tc)136 ATF_TC_HEAD(parse_X, tc)
137 {
138 atf_tc_set_md_var(tc, "descr",
139 "Verify parse_X services option too big.");
140 }
141
142 /* Initializes a parse struct from an input buffer of data. */
init_parse(struct parse * cfile,char * name,char * input)143 static void init_parse(struct parse *cfile, char* name, char *input) {
144 memset(cfile, 0, sizeof(struct parse));
145 cfile->tlname = name;
146 cfile->lpos = cfile->line = 1;
147 cfile->cur_line = cfile->line1;
148 cfile->prev_line = cfile->line2;
149 cfile->token_line = cfile->cur_line;
150 cfile->cur_line[0] = cfile->prev_line[0] = 0;
151 cfile->file = -1;
152 cfile->eol_token = 0;
153
154 cfile->inbuf = input;
155 cfile->buflen = strlen(input);
156 cfile->bufsiz = 0;
157 }
158
159 /*
160 * This test verifies that parse_X does not overwrite the output
161 * buffer when given input data that exceeds the output buffer
162 * capacity.
163 */
ATF_TC_BODY(parse_X,tc)164 ATF_TC_BODY(parse_X, tc)
165 {
166 struct parse cfile;
167 u_int8_t output[10];
168 unsigned len;
169
170 /* Input hex literal */
171 char *input = "01:02:03:04:05:06:07:08";
172 unsigned expected_len = 8;
173
174 /* Normal output plus two filler bytes */
175 u_int8_t expected_plus_two[] = {
176 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0xff, 0xff
177 };
178
179 /* Safe output when option is too long */
180 unsigned short_buf_len = 4;
181 u_int8_t expected_too_long[] = {
182 0x01, 0x02, 0x03, 0x04, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
183 };
184
185 /* First we'll run one that works normally */
186 memset(output, 0xff, sizeof(output));
187 init_parse(&cfile, "hex_fits", input);
188
189 len = parse_X(&cfile, output, expected_len);
190
191 // Len should match the expected len.
192 if (len != expected_len) {
193 atf_tc_fail("parse_X failed, output len: %d", len);
194 }
195
196 // We should not have written anything past the end of the buffer.
197 if (memcmp(output, expected_plus_two, sizeof(output))) {
198 atf_tc_fail("parse_X failed, output does not match expected");
199 }
200
201 // Now we'll try it with a buffer that's too small.
202 init_parse(&cfile, "hex_too_long", input);
203 memset(output, 0xff, sizeof(output));
204
205 len = parse_X(&cfile, output, short_buf_len);
206
207 // On errors, len should be zero.
208 if (len != 0) {
209 atf_tc_fail("parse_X failed, we should have had an error");
210 }
211
212 // We should not have written anything past the end of the buffer.
213 if (memcmp(output, expected_too_long, sizeof(output))) {
214 atf_tc_fail("parse_X overwrote buffer!");
215 }
216 }
217
218 ATF_TC(add_option_ref_cnt);
219
ATF_TC_HEAD(add_option_ref_cnt,tc)220 ATF_TC_HEAD(add_option_ref_cnt, tc)
221 {
222 atf_tc_set_md_var(tc, "descr",
223 "Verify add_option() does not leak option ref counts.");
224 }
225
ATF_TC_BODY(add_option_ref_cnt,tc)226 ATF_TC_BODY(add_option_ref_cnt, tc)
227 {
228 struct option_state *options = NULL;
229 struct option *option = NULL;
230 unsigned int cid_code = DHO_DHCP_CLIENT_IDENTIFIER;
231 char *cid_str = "1234";
232 int refcnt_before = 0;
233
234 // Look up the option we're going to add.
235 initialize_common_option_spaces();
236 if (!option_code_hash_lookup(&option, dhcp_universe.code_hash,
237 &cid_code, 0, MDL)) {
238 atf_tc_fail("cannot find option definition?");
239 }
240
241 // Get the option's reference count before we call add_options.
242 refcnt_before = option->refcnt;
243
244 // Allocate a option_state to which to add an option.
245 if (!option_state_allocate(&options, MDL)) {
246 atf_tc_fail("cannot allocat options state");
247 }
248
249 // Call add_option() to add the option to the option state.
250 if (!add_option(options, cid_code, cid_str, strlen(cid_str))) {
251 atf_tc_fail("add_option returned 0");
252 }
253
254 // Verify that calling add_option() only adds 1 to the option ref count.
255 if (option->refcnt != (refcnt_before + 1)) {
256 atf_tc_fail("after add_option(), count is wrong, before %d, after: %d",
257 refcnt_before, option->refcnt);
258 }
259
260 // Derefrence the option_state, this should reduce the ref count to
261 // it's starting value.
262 option_state_dereference(&options, MDL);
263
264 // Verify that dereferencing option_state restores option ref count.
265 if (option->refcnt != refcnt_before) {
266 atf_tc_fail("after state deref, count is wrong, before %d, after: %d",
267 refcnt_before, option->refcnt);
268 }
269 }
270
271 /* This macro defines main() method that will call specified
272 test cases. tp and simple_test_case names can be whatever you want
273 as long as it is a valid variable identifier. */
ATF_TP_ADD_TCS(tp)274 ATF_TP_ADD_TCS(tp)
275 {
276 ATF_TP_ADD_TC(tp, option_refcnt);
277 ATF_TP_ADD_TC(tp, pretty_print_option);
278 ATF_TP_ADD_TC(tp, parse_X);
279 ATF_TP_ADD_TC(tp, add_option_ref_cnt);
280
281 return (atf_no_error());
282 }
283