Line data Source code
1 : /*
2 : Unix SMB/CIFS implementation.
3 : SMB torture tester - mangling test
4 : Copyright (C) Andrew Tridgell 2002
5 : Copyright (C) David Mulder 2019
6 :
7 : This program is free software; you can redistribute it and/or modify
8 : it under the terms of the GNU General Public License as published by
9 : the Free Software Foundation; either version 3 of the License, or
10 : (at your option) any later version.
11 :
12 : This program is distributed in the hope that it will be useful,
13 : but WITHOUT ANY WARRANTY; without even the implied warranty of
14 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 : GNU General Public License for more details.
16 :
17 : You should have received a copy of the GNU General Public License
18 : along with this program. If not, see <http://www.gnu.org/licenses/>.
19 : */
20 :
21 : #include "includes.h"
22 : #include "system/filesys.h"
23 : #include "system/dir.h"
24 : #include <tdb.h>
25 : #include "../lib/util/util_tdb.h"
26 : #include "libcli/smb2/smb2.h"
27 : #include "libcli/smb2/smb2_calls.h"
28 : #include "torture/util.h"
29 : #include "torture/smb2/proto.h"
30 :
31 : #undef strcasecmp
32 :
33 : static TDB_CONTEXT *tdb;
34 :
35 : #define NAME_LENGTH 20
36 :
37 : static unsigned int total, collisions, failures;
38 :
39 50 : static bool test_one(struct torture_context *tctx, struct smb2_tree *tree,
40 : const char *name)
41 : {
42 0 : struct smb2_handle fnum;
43 0 : const char *shortname;
44 0 : const char *name2;
45 0 : NTSTATUS status;
46 0 : TDB_DATA data;
47 50 : struct smb2_create io = {0};
48 :
49 50 : total++;
50 :
51 50 : io.in.fname = name;
52 50 : io.in.desired_access = SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA |
53 : SEC_FILE_EXECUTE;
54 50 : io.in.create_disposition = NTCREATEX_DISP_CREATE;
55 50 : io.in.share_access = NTCREATEX_SHARE_ACCESS_READ |
56 : NTCREATEX_SHARE_ACCESS_WRITE |
57 : NTCREATEX_SHARE_ACCESS_DELETE;
58 50 : io.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
59 50 : status = smb2_create(tree, tree, &io);
60 50 : if (!NT_STATUS_IS_OK(status)) {
61 0 : torture_comment(tctx, "open of %s failed (%s)\n", name,
62 : nt_errstr(status));
63 0 : return false;
64 : }
65 50 : fnum = io.out.file.handle;
66 :
67 50 : status = smb2_util_close(tree, fnum);
68 50 : if (NT_STATUS_IS_ERR(status)) {
69 0 : torture_comment(tctx, "close of %s failed (%s)\n", name,
70 : nt_errstr(status));
71 0 : return false;
72 : }
73 :
74 : /* get the short name */
75 50 : status = smb2_qpathinfo_alt_name(tctx, tree, name, &shortname);
76 50 : if (!NT_STATUS_IS_OK(status)) {
77 0 : torture_comment(tctx, "query altname of %s failed (%s)\n",
78 : name, nt_errstr(status));
79 0 : return false;
80 : }
81 :
82 50 : name2 = talloc_asprintf(tctx, "mangle_test\\%s", shortname);
83 50 : status = smb2_util_unlink(tree, name2);
84 50 : if (NT_STATUS_IS_ERR(status)) {
85 0 : torture_comment(tctx, "unlink of %s (%s) failed (%s)\n",
86 : name2, name, nt_errstr(status));
87 0 : return false;
88 : }
89 :
90 : /* recreate by short name */
91 50 : io = (struct smb2_create){0};
92 50 : io.in.fname = name2;
93 50 : io.in.desired_access = SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA |
94 : SEC_FILE_EXECUTE;
95 50 : io.in.create_disposition = NTCREATEX_DISP_CREATE;
96 50 : io.in.share_access = NTCREATEX_SHARE_ACCESS_READ |
97 : NTCREATEX_SHARE_ACCESS_WRITE |
98 : NTCREATEX_SHARE_ACCESS_DELETE;
99 50 : io.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
100 50 : status = smb2_create(tree, tree, &io);
101 50 : if (!NT_STATUS_IS_OK(status)) {
102 0 : torture_comment(tctx, "open2 of %s failed (%s)\n", name2,
103 : nt_errstr(status));
104 0 : return false;
105 : }
106 50 : fnum = io.out.file.handle;
107 :
108 50 : status = smb2_util_close(tree, fnum);
109 50 : if (NT_STATUS_IS_ERR(status)) {
110 0 : torture_comment(tctx, "close of %s failed (%s)\n", name,
111 : nt_errstr(status));
112 0 : return false;
113 : }
114 :
115 : /* and unlink by long name */
116 50 : status = smb2_util_unlink(tree, name);
117 50 : if (NT_STATUS_IS_ERR(status)) {
118 0 : torture_comment(tctx, "unlink2 of %s (%s) failed (%s)\n",
119 : name, name2, nt_errstr(status));
120 0 : failures++;
121 0 : smb2_util_unlink(tree, name2);
122 0 : return true;
123 : }
124 :
125 : /* see if the short name is already in the tdb */
126 50 : data = tdb_fetch_bystring(tdb, shortname);
127 50 : if (data.dptr) {
128 : /* maybe its a duplicate long name? */
129 0 : if (strcasecmp(name, (const char *)data.dptr) != 0) {
130 : /* we have a collision */
131 0 : collisions++;
132 0 : torture_comment(tctx, "Collision between %s and %s"
133 : " -> %s (coll/tot: %u/%u)\n",
134 : name, data.dptr, shortname, collisions,
135 : total);
136 : }
137 0 : free(data.dptr);
138 : } else {
139 0 : TDB_DATA namedata;
140 : /* store it for later */
141 50 : namedata.dptr = discard_const_p(uint8_t, name);
142 50 : namedata.dsize = strlen(name)+1;
143 50 : tdb_store_bystring(tdb, shortname, namedata, TDB_REPLACE);
144 : }
145 :
146 50 : return true;
147 : }
148 :
149 :
150 50 : static char *gen_name(struct torture_context *tctx)
151 : {
152 50 : const char *chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz._-$~...";
153 50 : unsigned int max_idx = strlen(chars);
154 0 : unsigned int len;
155 0 : int i;
156 50 : char *p = NULL;
157 50 : char *name = NULL;
158 :
159 50 : name = talloc_strdup(tctx, "mangle_test\\");
160 50 : if (!name) {
161 0 : return NULL;
162 : }
163 :
164 50 : len = 1 + random() % NAME_LENGTH;
165 :
166 50 : name = talloc_realloc(tctx, name, char, strlen(name) + len + 6);
167 50 : if (!name) {
168 0 : return NULL;
169 : }
170 50 : p = name + strlen(name);
171 :
172 548 : for (i=0;i<len;i++) {
173 498 : p[i] = chars[random() % max_idx];
174 : }
175 :
176 50 : p[i] = 0;
177 :
178 50 : if (ISDOT(p) || ISDOTDOT(p)) {
179 0 : p[0] = '_';
180 : }
181 :
182 : /* have a high probability of a common lead char */
183 50 : if (random() % 2 == 0) {
184 21 : p[0] = 'A';
185 : }
186 :
187 : /* and a medium probability of a common lead string */
188 50 : if ((len > 5) && (random() % 10 == 0)) {
189 2 : strlcpy(p, "ABCDE", 6);
190 : }
191 :
192 : /* and a high probability of a good extension length */
193 50 : if (random() % 2 == 0) {
194 29 : char *s = strrchr(p, '.');
195 29 : if (s) {
196 10 : s[4] = 0;
197 : }
198 : }
199 :
200 50 : return name;
201 : }
202 :
203 :
204 5 : static bool torture_smb2_mangle(struct torture_context *torture,
205 : struct smb2_tree *tree)
206 : {
207 0 : extern int torture_numops;
208 0 : int i;
209 0 : bool ok;
210 0 : NTSTATUS status;
211 :
212 : /* we will use an internal tdb to store the names we have used */
213 5 : tdb = tdb_open(NULL, 100000, TDB_INTERNAL, 0, 0);
214 5 : torture_assert(torture, tdb, "ERROR: Failed to open tdb\n");
215 :
216 5 : ok = smb2_util_setup_dir(torture, tree, "mangle_test");
217 5 : torture_assert(torture, ok, "smb2_util_setup_dir failed\n");
218 :
219 55 : for (i=0;i<torture_numops;i++) {
220 0 : char *name;
221 :
222 50 : name = gen_name(torture);
223 50 : torture_assert(torture, name, "Name allocation failed\n");
224 :
225 50 : ok = test_one(torture, tree, name);
226 50 : torture_assert(torture, ok, talloc_asprintf(torture,
227 : "Mangle names failed with %s", name));
228 50 : if (total && total % 100 == 0) {
229 0 : if (torture_setting_bool(torture, "progress", true)) {
230 0 : torture_comment(torture,
231 : "collisions %u/%u - %.2f%% (%u failures)\r",
232 0 : collisions, total, (100.0*collisions) / total, failures);
233 : }
234 : }
235 : }
236 :
237 5 : smb2_util_unlink(tree, "mangle_test\\*");
238 5 : status = smb2_util_rmdir(tree, "mangle_test");
239 5 : torture_assert_ntstatus_ok(torture, status,
240 : "ERROR: Failed to remove directory\n");
241 :
242 5 : torture_comment(torture,
243 : "\nTotal collisions %u/%u - %.2f%% (%u failures)\n",
244 5 : collisions, total, (100.0*collisions) / total, failures);
245 :
246 5 : return (failures == 0);
247 : }
248 :
249 5 : static bool test_mangled_mask(struct torture_context *tctx,
250 : struct smb2_tree *tree)
251 : {
252 5 : bool ret = true;
253 5 : const char *dname = "single_find_with_mangled_name";
254 5 : const char *fname = "single_find_with_mangled_name\\verylongfilename";
255 5 : const char *shortname = NULL;
256 0 : NTSTATUS status;
257 5 : struct smb2_handle dh = {{0}};
258 5 : struct smb2_handle fh = {{0}};
259 0 : struct smb2_find f;
260 0 : union smb_search_data *d;
261 0 : unsigned count, i;
262 :
263 5 : torture_comment(tctx, "Checking find with mangled search mask\n");
264 :
265 5 : smb2_deltree(tree, dname);
266 :
267 5 : status = torture_smb2_testdir(tree, dname, &dh);
268 5 : torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
269 : "torture_smb2_testdir failed");
270 :
271 5 : status = torture_smb2_testfile(tree, fname, &fh);
272 5 : smb2_util_close(tree, fh);
273 :
274 5 : ZERO_STRUCT(f);
275 5 : f.in.file.handle = dh;
276 5 : f.in.pattern = "*";
277 5 : f.in.max_response_size = 0x1000;
278 5 : f.in.level = SMB2_FIND_BOTH_DIRECTORY_INFO;
279 :
280 5 : status = smb2_find_level(tree, tree, &f, &count, &d);
281 5 : torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
282 : "smb2_find_level failed\n");
283 5 : smb2_util_close(tree, dh);
284 5 : ZERO_STRUCT(dh);
285 :
286 15 : for (i = 0; i < count; i++) {
287 15 : const char *found = d[i].both_directory_info.name.s;
288 :
289 15 : if (!strcmp(found, ".") || !strcmp(found, "..")) {
290 10 : continue;
291 : }
292 :
293 5 : torture_assert_str_equal_goto(tctx, found, "verylongfilename", ret, done,
294 : "Bad filename\n");
295 5 : shortname = d[i].both_directory_info.short_name.s;
296 5 : break;
297 : }
298 :
299 5 : torture_assert_not_null_goto(tctx, shortname, ret, done,
300 : "shortname is NULL\n");
301 :
302 5 : torture_comment(tctx, "Got shortname: %s\n", shortname);
303 :
304 5 : status = torture_smb2_testdir(tree, dname, &dh);
305 :
306 5 : ZERO_STRUCT(f);
307 5 : f.in.file.handle = dh;
308 5 : f.in.continue_flags = SMB2_CONTINUE_FLAG_SINGLE;
309 5 : f.in.pattern = shortname;
310 5 : f.in.max_response_size = 0x1000;
311 5 : f.in.level = SMB2_FIND_BOTH_DIRECTORY_INFO;
312 :
313 5 : status = smb2_find_level(tree, tree, &f, &count, &d);
314 5 : torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
315 : "smb2_find_level failed\n");
316 5 : smb2_util_close(tree, dh);
317 5 : ZERO_STRUCT(dh);
318 :
319 5 : done:
320 5 : if (!smb2_util_handle_empty(fh)) {
321 5 : smb2_util_close(tree, fh);
322 : }
323 5 : if (!smb2_util_handle_empty(dh)) {
324 0 : smb2_util_close(tree, dh);
325 : }
326 5 : smb2_deltree(tree, dname);
327 5 : return ret;
328 :
329 : }
330 :
331 2354 : struct torture_suite *torture_smb2_name_mangling_init(TALLOC_CTX *ctx)
332 : {
333 2354 : struct torture_suite *suite = NULL;
334 :
335 2354 : suite = torture_suite_create(ctx, "name-mangling");
336 2354 : suite->description = talloc_strdup(suite, "SMB2 name mangling tests");
337 :
338 2354 : torture_suite_add_1smb2_test(suite, "mangle", torture_smb2_mangle);
339 2354 : torture_suite_add_1smb2_test(suite, "mangled-mask", test_mangled_mask);
340 2354 : return suite;
341 : }
|