Line data Source code
1 : /*
2 : * Unix SMB/CIFS implementation.
3 : *
4 : * block SMB2 transports using iptables
5 : *
6 : * Copyright (C) Guenther Deschner, 2017
7 : *
8 : * This program is free software; you can redistribute it and/or modify
9 : * it under the terms of the GNU General Public License as published by
10 : * the Free Software Foundation; either version 3 of the License, or
11 : * (at your option) any later version.
12 : *
13 : * This program is distributed in the hope that it will be useful,
14 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 : * GNU General Public License for more details.
17 : *
18 : * You should have received a copy of the GNU General Public License
19 : * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 : */
21 :
22 : #include "includes.h"
23 : #include "libcli/smb2/smb2.h"
24 : #include "torture/torture.h"
25 : #include "torture/smb2/proto.h"
26 : #include "system/network.h"
27 : #include "lib/util/util_net.h"
28 : #include "torture/smb2/block.h"
29 : #include "libcli/smb/smbXcli_base.h"
30 : #include "lib/util/tevent_ntstatus.h"
31 : #include "oplock_break_handler.h"
32 : #include "lease_break_handler.h"
33 :
34 : /*
35 : * OUTPUT
36 : * |
37 : * -----> SMBTORTURE_OUTPUT
38 : * |
39 : * -----> SMBTORTURE_transportname1
40 : * -----> SMBTORTURE_transportname2
41 : */
42 :
43 :
44 0 : static bool run_cmd(const char *cmd)
45 : {
46 0 : int ret;
47 :
48 0 : DEBUG(10, ("%s will call '%s'\n", __location__, cmd));
49 :
50 0 : ret = system(cmd);
51 0 : if (ret) {
52 0 : DEBUG(1, ("%s failed to execute system call: %s: %d\n",
53 : __location__, cmd, ret));
54 0 : return false;
55 : }
56 :
57 0 : return true;
58 : }
59 :
60 0 : static const char *iptables_command(struct torture_context *tctx)
61 : {
62 0 : return torture_setting_string(tctx, "iptables_command",
63 : "/usr/sbin/iptables");
64 : }
65 :
66 : char *escape_shell_string(const char *src);
67 :
68 : /*
69 : * iptables v1.6.1: chain name `SMBTORTURE_INPUT_tree1->session->transport'
70 : * too long (must be under 29 chars)
71 : *
72 : * maybe truncate chainname ?
73 : */
74 0 : static const char *samba_chain_name(struct torture_context *tctx,
75 : const char *name,
76 : const char *prefix)
77 : {
78 0 : const char *s;
79 0 : char *sm;
80 :
81 0 : s = talloc_asprintf(tctx, "%s_%s", prefix, name);
82 0 : if (s == NULL) {
83 0 : return NULL;
84 : }
85 :
86 0 : sm = escape_shell_string(s);
87 0 : if (sm == NULL) {
88 0 : return NULL;
89 : }
90 :
91 0 : s = talloc_strdup(tctx, sm);
92 0 : free(sm);
93 :
94 0 : return s;
95 : }
96 :
97 0 : static bool iptables_setup_chain(struct torture_context *tctx,
98 : const char *parent_chain,
99 : const char *chain,
100 : bool unblock)
101 : {
102 0 : const char *ipt = iptables_command(tctx);
103 0 : const char *cmd;
104 :
105 0 : if (unblock) {
106 0 : cmd = talloc_asprintf(tctx,
107 : "%s -L %s > /dev/null 2>&1 && "
108 : "("
109 : "%s -F %s;"
110 : "%s -D %s -j %s > /dev/null 2>&1 || true;"
111 : "%s -X %s;"
112 : ");"
113 : "%s -L %s > /dev/null 2>&1 || true;",
114 : ipt, chain,
115 : ipt, chain,
116 : ipt, parent_chain, chain,
117 : ipt, chain,
118 : ipt, chain);
119 : } else {
120 0 : cmd = talloc_asprintf(tctx,
121 : "%s -L %s > /dev/null 2>&1 || "
122 : "("
123 : "%s -N %s && "
124 : "%s -I %s -j %s;"
125 : ");"
126 : "%s -F %s;",
127 : ipt, chain,
128 : ipt, chain,
129 : ipt, parent_chain, chain,
130 : ipt, chain);
131 : }
132 :
133 0 : if (cmd == NULL) {
134 0 : return false;
135 : }
136 :
137 0 : if (!run_cmd(cmd)) {
138 0 : return false;
139 : }
140 :
141 0 : return true;
142 : }
143 :
144 857 : uint16_t torture_get_local_port_from_transport(struct smb2_transport *t)
145 : {
146 0 : const struct sockaddr_storage *local_ss;
147 :
148 857 : local_ss = smbXcli_conn_local_sockaddr(t->conn);
149 :
150 857 : return get_sockaddr_port(local_ss);
151 : }
152 :
153 0 : static bool torture_block_tcp_output_port_internal(
154 : struct torture_context *tctx,
155 : const char *name,
156 : uint16_t port,
157 : bool unblock)
158 : {
159 0 : const char *ipt = iptables_command(tctx);
160 0 : const char *chain_out = NULL;
161 0 : char *cmd_out = NULL;
162 :
163 0 : chain_out = samba_chain_name(tctx, name, "SMBTORTURE");
164 0 : if (chain_out == NULL) {
165 0 : return false;
166 : }
167 :
168 0 : torture_comment(tctx, "%sblocking %s dport %d\n",
169 : unblock ? "un" : "", name, port);
170 :
171 0 : if (!unblock) {
172 0 : bool ok;
173 :
174 0 : iptables_setup_chain(tctx,
175 : "SMBTORTURE_OUTPUT",
176 : chain_out,
177 : true);
178 0 : ok = iptables_setup_chain(tctx,
179 : "SMBTORTURE_OUTPUT",
180 : chain_out,
181 : false);
182 0 : if (!ok) {
183 0 : return false;
184 : }
185 : }
186 :
187 0 : cmd_out = talloc_asprintf(tctx,
188 : "%s %s %s -p tcp --sport %d -j DROP",
189 : ipt, unblock ? "-D" : "-I", chain_out, port);
190 0 : if (cmd_out == NULL) {
191 0 : return false;
192 : }
193 :
194 0 : if (!run_cmd(cmd_out)) {
195 0 : return false;
196 : }
197 :
198 0 : if (unblock) {
199 0 : bool ok;
200 :
201 0 : ok = iptables_setup_chain(tctx,
202 : "SMBTORTURE_OUTPUT",
203 : chain_out,
204 : true);
205 0 : if (!ok) {
206 0 : return false;
207 : }
208 : }
209 :
210 0 : return true;
211 : }
212 :
213 0 : bool torture_block_tcp_output_port(struct torture_context *tctx,
214 : const char *name,
215 : uint16_t port)
216 : {
217 0 : return torture_block_tcp_output_port_internal(tctx, name, port, false);
218 : }
219 :
220 0 : bool torture_unblock_tcp_output_port(struct torture_context *tctx,
221 : const char *name,
222 : uint16_t port)
223 : {
224 0 : return torture_block_tcp_output_port_internal(tctx, name, port, true);
225 : }
226 :
227 0 : bool torture_block_tcp_output_setup(struct torture_context *tctx)
228 : {
229 0 : return iptables_setup_chain(tctx, "OUTPUT", "SMBTORTURE_OUTPUT", false);
230 : }
231 :
232 0 : bool torture_unblock_tcp_output_cleanup(struct torture_context *tctx)
233 : {
234 0 : return iptables_setup_chain(tctx, "OUTPUT", "SMBTORTURE_OUTPUT", true);
235 : }
236 :
237 : /*
238 : * Use iptables to block channels
239 : */
240 0 : static bool test_block_smb2_transport_iptables(struct torture_context *tctx,
241 : struct smb2_transport *transport,
242 : const char *name)
243 : {
244 0 : uint16_t local_port;
245 0 : bool ret;
246 :
247 0 : local_port = torture_get_local_port_from_transport(transport);
248 0 : torture_comment(tctx, "transport[%s] uses tcp port: %d\n", name, local_port);
249 0 : ret = torture_block_tcp_output_port(tctx, name, local_port);
250 0 : torture_assert(tctx, ret, "we could not block tcp transport");
251 :
252 0 : return ret;
253 : }
254 :
255 0 : static bool test_unblock_smb2_transport_iptables(struct torture_context *tctx,
256 : struct smb2_transport *transport,
257 : const char *name)
258 : {
259 0 : uint16_t local_port;
260 0 : bool ret;
261 :
262 0 : local_port = torture_get_local_port_from_transport(transport);
263 0 : torture_comment(tctx, "transport[%s] uses tcp port: %d\n", name, local_port);
264 0 : ret = torture_unblock_tcp_output_port(tctx, name, local_port);
265 0 : torture_assert(tctx, ret, "we could not block tcp transport");
266 :
267 0 : return ret;
268 : }
269 :
270 66 : static bool torture_blocked_lease_handler(struct smb2_transport *transport,
271 : const struct smb2_lease_break *lb,
272 : void *private_data)
273 : {
274 0 : struct smb2_transport *transport_copy =
275 66 : talloc_get_type_abort(private_data,
276 : struct smb2_transport);
277 66 : bool lease_skip_ack = lease_break_info.lease_skip_ack;
278 0 : bool ok;
279 :
280 66 : lease_break_info.lease_skip_ack = true;
281 66 : ok = transport_copy->lease.handler(transport,
282 : lb,
283 : transport_copy->lease.private_data);
284 66 : lease_break_info.lease_skip_ack = lease_skip_ack;
285 :
286 66 : if (!ok) {
287 0 : return false;
288 : }
289 :
290 66 : if (lease_break_info.lease_skip_ack) {
291 64 : return true;
292 : }
293 :
294 2 : if (lb->break_flags & SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED) {
295 2 : lease_break_info.failures++;
296 : }
297 :
298 2 : return true;
299 : }
300 :
301 134 : static bool torture_blocked_oplock_handler(struct smb2_transport *transport,
302 : const struct smb2_handle *handle,
303 : uint8_t level,
304 : void *private_data)
305 : {
306 0 : struct smb2_transport *transport_copy =
307 134 : talloc_get_type_abort(private_data,
308 : struct smb2_transport);
309 134 : bool oplock_skip_ack = break_info.oplock_skip_ack;
310 0 : bool ok;
311 :
312 134 : break_info.oplock_skip_ack = true;
313 134 : ok = transport_copy->oplock.handler(transport,
314 : handle,
315 : level,
316 : transport_copy->oplock.private_data);
317 134 : break_info.oplock_skip_ack = oplock_skip_ack;
318 :
319 134 : if (!ok) {
320 0 : return false;
321 : }
322 :
323 134 : if (break_info.oplock_skip_ack) {
324 128 : return true;
325 : }
326 :
327 6 : break_info.failures++;
328 6 : break_info.failure_status = NT_STATUS_CONNECTION_DISCONNECTED;
329 :
330 6 : return true;
331 : }
332 :
333 393 : static bool test_block_smb2_transport_fsctl_smbtorture(struct torture_context *tctx,
334 : struct smb2_transport *transport,
335 : const char *name)
336 : {
337 393 : struct smb2_transport *transport_copy = NULL;
338 393 : DATA_BLOB in_input_buffer = data_blob_null;
339 393 : DATA_BLOB in_output_buffer = data_blob_null;
340 393 : DATA_BLOB out_input_buffer = data_blob_null;
341 393 : DATA_BLOB out_output_buffer = data_blob_null;
342 393 : struct tevent_req *req = NULL;
343 0 : uint16_t local_port;
344 0 : NTSTATUS status;
345 0 : bool ok;
346 :
347 393 : transport_copy = talloc_zero(transport, struct smb2_transport);
348 393 : torture_assert(tctx, transport_copy, "talloc transport_copy");
349 393 : transport_copy->lease = transport->lease;
350 393 : transport_copy->oplock = transport->oplock;
351 :
352 393 : local_port = torture_get_local_port_from_transport(transport);
353 393 : torture_comment(tctx, "transport[%s] uses tcp port: %d\n", name, local_port);
354 393 : req = smb2cli_ioctl_send(tctx,
355 : tctx->ev,
356 : transport->conn,
357 : 1000, /* timeout_msec */
358 : NULL, /* session */
359 : NULL, /* tcon */
360 : UINT64_MAX, /* in_fid_persistent */
361 : UINT64_MAX, /* in_fid_volatile */
362 : FSCTL_SMBTORTURE_FORCE_UNACKED_TIMEOUT,
363 : 0, /* in_max_input_length */
364 : &in_input_buffer,
365 : 0, /* in_max_output_length */
366 : &in_output_buffer,
367 : SMB2_IOCTL_FLAG_IS_FSCTL);
368 393 : torture_assert(tctx, req != NULL, "smb2cli_ioctl_send() failed");
369 393 : ok = tevent_req_poll_ntstatus(req, tctx->ev, &status);
370 393 : if (ok) {
371 393 : status = NT_STATUS_OK;
372 : }
373 393 : torture_assert_ntstatus_ok(tctx, status, "tevent_req_poll_ntstatus() failed");
374 393 : status = smb2cli_ioctl_recv(req, tctx,
375 : &out_input_buffer,
376 : &out_output_buffer);
377 393 : torture_assert_ntstatus_ok(tctx, status,
378 : "FSCTL_SMBTORTURE_FORCE_UNACKED_TIMEOUT failed\n\n"
379 : "On a Samba server 'smbd:FSCTL_SMBTORTURE = yes' is needed!\n\n"
380 : "Otherwise you may need to use iptables like this:\n"
381 : "--option='torture:use_iptables=yes'\n"
382 : "And maybe something like this in addition:\n"
383 : "--option='torture:iptables_command=sudo /sbin/iptables'\n\n");
384 392 : TALLOC_FREE(req);
385 :
386 392 : if (transport->lease.handler != NULL) {
387 390 : transport->lease.handler = torture_blocked_lease_handler;
388 390 : transport->lease.private_data = transport_copy;
389 : }
390 392 : if (transport->oplock.handler != NULL) {
391 392 : transport->oplock.handler = torture_blocked_oplock_handler;
392 392 : transport->oplock.private_data = transport_copy;
393 : }
394 :
395 392 : return true;
396 : }
397 :
398 393 : bool _test_block_smb2_transport(struct torture_context *tctx,
399 : struct smb2_transport *transport,
400 : const char *name)
401 : {
402 393 : bool use_iptables = torture_setting_bool(tctx,
403 : "use_iptables", false);
404 :
405 393 : if (use_iptables) {
406 0 : return test_block_smb2_transport_iptables(tctx, transport, name);
407 : } else {
408 393 : return test_block_smb2_transport_fsctl_smbtorture(tctx, transport, name);
409 : }
410 : }
411 :
412 392 : bool _test_unblock_smb2_transport(struct torture_context *tctx,
413 : struct smb2_transport *transport,
414 : const char *name)
415 : {
416 392 : bool use_iptables = torture_setting_bool(tctx,
417 : "use_iptables", false);
418 :
419 392 : if (use_iptables) {
420 0 : return test_unblock_smb2_transport_iptables(tctx, transport, name);
421 : } else {
422 392 : return true;
423 : }
424 : }
425 :
426 51 : bool test_setup_blocked_transports(struct torture_context *tctx)
427 : {
428 51 : bool use_iptables = torture_setting_bool(tctx,
429 : "use_iptables", false);
430 :
431 51 : if (use_iptables) {
432 0 : return torture_block_tcp_output_setup(tctx);
433 : }
434 :
435 51 : return true;
436 : }
437 :
438 50 : void test_cleanup_blocked_transports(struct torture_context *tctx)
439 : {
440 50 : bool use_iptables = torture_setting_bool(tctx,
441 : "use_iptables", false);
442 :
443 50 : if (use_iptables) {
444 0 : torture_unblock_tcp_output_cleanup(tctx);
445 : }
446 50 : }
|