Line data Source code
1 : /*
2 : Unix SMB/CIFS implementation.
3 :
4 : Copyright (C) Andrew Walker (2025)
5 :
6 : This program is free software; you can redistribute it and/or modify
7 : it under the terms of the GNU General Public License as published by
8 : the Free Software Foundation; either version 3 of the License, or
9 : (at your option) any later version.
10 :
11 : This program is distributed in the hope that it will be useful,
12 : but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : GNU General Public License for more details.
15 :
16 : You should have received a copy of the GNU General Public License
17 : along with this program. If not, see <http://www.gnu.org/licenses/>.
18 : */
19 :
20 : #include "includes.h"
21 : #include "lib/cmdline/cmdline.h"
22 : #include "libcli/smb2/smb2.h"
23 : #include "libcli/smb2/smb2_calls.h"
24 : #include "libcli/smb/smbXcli_base.h"
25 : #include "torture/torture.h"
26 : #include "torture/vfs/proto.h"
27 : #include "libcli/resolve/resolve.h"
28 : #include "torture/util.h"
29 : #include "torture/smb2/proto.h"
30 : #include "lib/param/param.h"
31 :
32 : #define BASEDIR "smb2-testads"
33 :
34 :
35 0 : static bool get_stream_handle(struct torture_context *tctx,
36 : struct smb2_tree *tree,
37 : const char *dname,
38 : const char *fname,
39 : const char *sname,
40 : struct smb2_handle *hdl_in)
41 : {
42 0 : bool ret = true;
43 : NTSTATUS status;
44 0 : struct smb2_handle fhandle = {{0}};
45 0 : struct smb2_handle dhandle = {{0}};
46 :
47 0 : torture_comment(tctx, "Create dir\n");
48 :
49 0 : status = torture_smb2_testdir(tree, dname, &dhandle);
50 0 : torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testdir\n");
51 :
52 0 : torture_comment(tctx, "Create file\n");
53 :
54 0 : status = torture_smb2_testfile(tree, fname, &fhandle);
55 0 : torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testfile\n");
56 :
57 0 : status = torture_smb2_testfile(tree, sname, hdl_in);
58 0 : torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testfile\n");
59 :
60 0 : done:
61 0 : if (!smb2_util_handle_empty(fhandle)) {
62 0 : smb2_util_close(tree, fhandle);
63 : }
64 0 : if (!smb2_util_handle_empty(dhandle)) {
65 0 : smb2_util_close(tree, dhandle);
66 : }
67 0 : return ret;
68 : }
69 :
70 0 : static bool read_stream(struct torture_context *tctx,
71 : TALLOC_CTX *mem_ctx,
72 : struct smb2_tree *tree,
73 : struct smb2_handle *stream_hdl,
74 : off_t read_offset,
75 : size_t read_count,
76 : char **data_out,
77 : size_t *data_out_sz)
78 : {
79 : NTSTATUS status;
80 : struct smb2_read r;
81 0 : bool ret = true;
82 :
83 0 : ZERO_STRUCT(r);
84 0 : r.in.file.handle = *stream_hdl;
85 0 : r.in.length = read_count;
86 0 : r.in.offset = read_offset;
87 :
88 0 : status = smb2_read(tree, mem_ctx, &r);
89 0 : torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "stream read\n");
90 :
91 0 : *data_out = (char *)r.out.data.data;
92 0 : *data_out_sz = r.out.data.length;
93 :
94 0 : done:
95 0 : return ret;
96 : }
97 :
98 :
99 : #define WRITE_PAYLOAD "canary"
100 : #define ADS_LEN 1024
101 : #define ADS_OFF_TAIL ADS_LEN - sizeof(WRITE_PAYLOAD)
102 :
103 0 : static bool test_streams_pwrite_hole(struct torture_context *tctx,
104 : struct smb2_tree *tree)
105 : {
106 : NTSTATUS status;
107 : bool ok;
108 0 : bool ret = true;
109 0 : const char *dname = BASEDIR "\\testdir";
110 0 : const char *fname = BASEDIR "\\testdir\\testfile";
111 0 : const char *sname = BASEDIR "\\testdir\\testfile:test_stream";
112 0 : const char *canary = "canary";
113 0 : struct smb2_handle shandle = {{0}};
114 0 : TALLOC_CTX *tmp_ctx = NULL;
115 0 : char *data = NULL;
116 : size_t data_sz, i;
117 :
118 0 : ok = smb2_util_setup_dir(tctx, tree, BASEDIR);
119 0 : torture_assert_goto(tctx, ok == true, ret, done, "Unable to setup testdir\n");
120 :
121 0 : tmp_ctx = talloc_new(tree);
122 0 : torture_assert_goto(tctx, tmp_ctx != NULL, ret, done, "Memory failure\n");
123 :
124 0 : ok = get_stream_handle(tctx, tree, dname, fname, sname, &shandle);
125 0 : if (!ok) {
126 : // torture assert already set
127 0 : goto done;
128 : }
129 :
130 : /*
131 : * We're going to write a string at the beginning at the ADS, then write the same
132 : * string at a later offset, introducing a hole in the file
133 : */
134 0 : torture_comment(tctx, "writing at varying offsets to create hole\n");
135 0 : status = smb2_util_write(tree, shandle, WRITE_PAYLOAD, 0, sizeof(WRITE_PAYLOAD));
136 0 : if (!NT_STATUS_IS_OK(status)) {
137 0 : torture_comment(tctx, "Failed to write %zu bytes to "
138 : "stream at offset 0\n", sizeof(canary));
139 0 : return false;
140 : }
141 :
142 0 : status = smb2_util_write(tree, shandle, WRITE_PAYLOAD, ADS_OFF_TAIL, sizeof(WRITE_PAYLOAD));
143 0 : if (!NT_STATUS_IS_OK(status)) {
144 0 : torture_comment(tctx, "Failed to write %zu bytes to "
145 : "stream at offset 1018\n", sizeof(canary));
146 0 : return false;
147 : }
148 :
149 : /* Now we'll read the stream contents */
150 0 : torture_comment(tctx, "Read stream data\n");
151 0 : ok = read_stream(tctx, tmp_ctx, tree, &shandle, 0, ADS_LEN, &data, &data_sz);
152 0 : if (!ok) {
153 : // torture assert already set
154 0 : goto done;
155 : }
156 :
157 0 : torture_assert_goto(tctx, data_sz == ADS_LEN, ret, done, "Short read on ADS\n");
158 :
159 : /* Make sure our strings actually got written */
160 0 : if (strncmp(data, WRITE_PAYLOAD, sizeof(WRITE_PAYLOAD)) != 0) {
161 0 : torture_result(tctx, TORTURE_FAIL,
162 : "Payload write at beginning of file failed");
163 0 : ret = false;
164 0 : goto done;
165 : }
166 :
167 0 : if (strncmp(data + ADS_OFF_TAIL, WRITE_PAYLOAD, sizeof(WRITE_PAYLOAD)) != 0) {
168 0 : torture_result(tctx, TORTURE_FAIL,
169 : "Payload write at end of file failed");
170 0 : ret = false;
171 0 : goto done;
172 : }
173 :
174 : /* Now we'll check that the hole is full of null bytes */
175 0 : for (i = sizeof(WRITE_PAYLOAD); i < ADS_OFF_TAIL; i++) {
176 0 : if (data[i] != '\0') {
177 0 : torture_comment(tctx, "idx: %zu, got 0x%02x when expected 0x00\n",
178 0 : i, (uint8_t)data[i]);
179 0 : torture_result(tctx, TORTURE_FAIL,
180 : "0x%08x: unexpected non-null byte in ADS read\n",
181 0 : data[i]);
182 0 : ret = false;
183 0 : goto done;
184 : }
185 : }
186 :
187 0 : done:
188 0 : talloc_free(tmp_ctx);
189 :
190 0 : if (!smb2_util_handle_empty(shandle)) {
191 0 : smb2_util_close(tree, shandle);
192 : }
193 :
194 0 : smb2_deltree(tree, BASEDIR);
195 :
196 0 : return ret;
197 : }
198 :
199 : /*
200 : basic testing of vfs_streams_xattr
201 : */
202 964 : struct torture_suite *torture_vfs_streams_xattr(TALLOC_CTX *ctx)
203 : {
204 964 : struct torture_suite *suite = torture_suite_create(ctx, "streams_xattr");
205 :
206 964 : torture_suite_add_1smb2_test(suite, "streams-pwrite-hole", test_streams_pwrite_hole);
207 :
208 964 : suite->description = talloc_strdup(suite, "vfs_streams_xattr tests");
209 :
210 964 : return suite;
211 : }
|