Skip to content

Commit e06cde0

Browse files
committed
server: Wait until handshake complete before calling .open callback
Currently we call the plugin .open callback as soon as we receive a TCP connection: $ nbdkit -fv --tls=require --tls-certificates=tests/pki null \ --run "telnet localhost 10809" [...] Trying ::1... Connected to localhost. Escape character is '^]'. nbdkit: debug: accepted connection nbdkit: debug: null: open readonly=0 ◀ NOTE nbdkit: null[1]: debug: newstyle negotiation: flags: global 0x3 NBDMAGICIHAVEOPT In plugins such as curl, guestfs, ssh, vddk and others we do a considerable amount of work in the .open callback (such as making a remote connection or launching an appliance). Therefore we are providing an easy Denial of Service / Amplification Attack for unauthorized clients to cause a lot of work to be done for only the cost of a simple TCP 3 way handshake. This commit moves the call to the .open callback after the NBD handshake has mostly completed. In particular TLS authentication must be complete before we will call into the plugin. It is unlikely that there are plugins which really depend on the current behaviour of .open (which I found surprising even though I guess I must have written it). If there are then we could add a new .connect callback or similar to allow plugins to get control at the earlier point in the connection. After this change you can see that the .open callback is not called from just a simple TCP connection: $ ./nbdkit -fv --tls=require --tls-certificates=tests/pki null \ --run "telnet localhost 10809" [...] Trying ::1... Connected to localhost. Escape character is '^]'. nbdkit: debug: accepted connection nbdkit: null[1]: debug: newstyle negotiation: flags: global 0x3 NBDMAGICIHAVEOPT xx nbdkit: null[1]: debug: newstyle negotiation: client flags: 0xd0a7878 nbdkit: null[1]: error: client requested unknown flags 0xd0a7878 Connection closed by foreign host. nbdkit: debug: null: unload plugin Signed-off-by: Richard W.M. Jones <rjones@redhat.com> (cherry picked from commit c05686f)
1 parent 33a1c03 commit e06cde0

File tree

5 files changed

+57
-62
lines changed

5 files changed

+57
-62
lines changed

server/connections.c

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -174,12 +174,6 @@ _handle_single_connection (int sockin, int sockout)
174174
if (!conn)
175175
goto done;
176176

177-
lock_request (conn);
178-
r = backend->open (backend, conn, readonly);
179-
unlock_request (conn);
180-
if (r == -1)
181-
goto done;
182-
183177
/* NB: because of an asynchronous exit backend can be set to NULL at
184178
* just about any time.
185179
*/
@@ -189,17 +183,11 @@ _handle_single_connection (int sockin, int sockout)
189183
plugin_name = "(unknown)";
190184
threadlocal_set_name (plugin_name);
191185

192-
/* Prepare (for filters), called just after open. */
193-
lock_request (conn);
194-
if (backend)
195-
r = backend->prepare (backend, conn);
196-
else
197-
r = 0;
198-
unlock_request (conn);
199-
if (r == -1)
200-
goto done;
201-
202-
/* Handshake. */
186+
/* NBD handshake.
187+
*
188+
* Note that this calls the backend .open callback when it is safe
189+
* to do so (eg. after TLS authentication).
190+
*/
203191
if (protocol_handshake (conn) == -1)
204192
goto done;
205193

server/internal.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,9 @@ extern int connection_set_status (struct connection *conn, int value)
198198
/* protocol-handshake.c */
199199
extern int protocol_handshake (struct connection *conn)
200200
__attribute__((__nonnull__ (1)));
201-
extern int protocol_compute_eflags (struct connection *conn, uint16_t *flags)
202-
__attribute__((__nonnull__ (1, 2)));
201+
extern int protocol_common_open (struct connection *conn,
202+
uint64_t *exportsize, uint16_t *flags)
203+
__attribute__((__nonnull__ (1, 2, 3)));
203204

204205
/* protocol-handshake-oldstyle.c */
205206
extern int protocol_handshake_oldstyle (struct connection *conn)

server/protocol-handshake-newstyle.c

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -203,19 +203,7 @@ conn_recv_full (struct connection *conn, void *buf, size_t len,
203203
static int
204204
finish_newstyle_options (struct connection *conn)
205205
{
206-
int64_t r;
207-
208-
r = backend->get_size (backend, conn);
209-
if (r == -1)
210-
return -1;
211-
if (r < 0) {
212-
nbdkit_error (".get_size function returned invalid value "
213-
"(%" PRIi64 ")", r);
214-
return -1;
215-
}
216-
conn->exportsize = (uint64_t) r;
217-
218-
if (protocol_compute_eflags (conn, &conn->eflags) < 0)
206+
if (protocol_common_open (conn, &conn->exportsize, &conn->eflags) == -1)
219207
return -1;
220208

221209
debug ("newstyle negotiation: flags: export 0x%x", conn->eflags);

server/protocol-handshake-oldstyle.c

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ int
4747
protocol_handshake_oldstyle (struct connection *conn)
4848
{
4949
struct old_handshake handshake;
50-
int64_t r;
5150
uint64_t exportsize;
5251
uint16_t gflags, eflags;
5352

@@ -59,21 +58,11 @@ protocol_handshake_oldstyle (struct connection *conn)
5958
return -1;
6059
}
6160

62-
r = backend->get_size (backend, conn);
63-
if (r == -1)
61+
if (protocol_common_open (conn, &exportsize, &eflags) == -1)
6462
return -1;
65-
if (r < 0) {
66-
nbdkit_error (".get_size function returned invalid value "
67-
"(%" PRIi64 ")", r);
68-
return -1;
69-
}
70-
exportsize = (uint64_t) r;
7163
conn->exportsize = exportsize;
7264

7365
gflags = 0;
74-
if (protocol_compute_eflags (conn, &eflags) < 0)
75-
return -1;
76-
7766
debug ("oldstyle negotiation: flags: global 0x%x export 0x%x",
7867
gflags, eflags);
7968

server/protocol-handshake.c

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,58 @@
4343
#include "byte-swapping.h"
4444
#include "protocol.h"
4545

46-
/* eflags calculation is the same between oldstyle and newstyle
47-
* protocols.
46+
int
47+
protocol_handshake (struct connection *conn)
48+
{
49+
int r;
50+
51+
lock_request (conn);
52+
if (!newstyle)
53+
r = protocol_handshake_oldstyle (conn);
54+
else
55+
r = protocol_handshake_newstyle (conn);
56+
unlock_request (conn);
57+
58+
return r;
59+
}
60+
61+
/* Common code used by oldstyle and newstyle protocols to:
62+
*
63+
* - call the backend .open method
64+
*
65+
* - get the export size
66+
*
67+
* - compute the eflags (same between oldstyle and newstyle
68+
* protocols)
69+
*
70+
* The protocols must defer this as late as possible so that
71+
* unauthorized clients can't cause unnecessary work in .open by
72+
* simply opening a TCP connection.
4873
*/
4974
int
50-
protocol_compute_eflags (struct connection *conn, uint16_t *flags)
75+
protocol_common_open (struct connection *conn,
76+
uint64_t *exportsize, uint16_t *flags)
5177
{
78+
int64_t size;
5279
uint16_t eflags = NBD_FLAG_HAS_FLAGS;
5380
int fl;
5481

82+
if (backend->open (backend, conn, readonly) == -1)
83+
return -1;
84+
85+
/* Prepare (for filters), called just after open. */
86+
if (backend->prepare (backend, conn) == -1)
87+
return -1;
88+
89+
size = backend->get_size (backend, conn);
90+
if (size == -1)
91+
return -1;
92+
if (size < 0) {
93+
nbdkit_error (".get_size function returned invalid value "
94+
"(%" PRIi64 ")", size);
95+
return -1;
96+
}
97+
5598
fl = backend->can_write (backend, conn);
5699
if (fl == -1)
57100
return -1;
@@ -136,21 +179,7 @@ protocol_compute_eflags (struct connection *conn, uint16_t *flags)
136179
if (conn->structured_replies)
137180
eflags |= NBD_FLAG_SEND_DF;
138181

182+
*exportsize = size;
139183
*flags = eflags;
140184
return 0;
141185
}
142-
143-
int
144-
protocol_handshake (struct connection *conn)
145-
{
146-
int r;
147-
148-
lock_request (conn);
149-
if (!newstyle)
150-
r = protocol_handshake_oldstyle (conn);
151-
else
152-
r = protocol_handshake_newstyle (conn);
153-
unlock_request (conn);
154-
155-
return r;
156-
}

0 commit comments

Comments
 (0)