summaryrefslogtreecommitdiff
path: root/source3/smbd/process.c
diff options
context:
space:
mode:
Diffstat (limited to 'source3/smbd/process.c')
-rw-r--r--source3/smbd/process.c236
1 files changed, 236 insertions, 0 deletions
diff --git a/source3/smbd/process.c b/source3/smbd/process.c
index f87eccfdcd..b83e3ddcde 100644
--- a/source3/smbd/process.c
+++ b/source3/smbd/process.c
@@ -1880,6 +1880,242 @@ static bool smb_splice_chain(uint8_t **poutbuf, const uint8_t *andx_buf)
return true;
}
+bool smb1_is_chain(const uint8_t *buf)
+{
+ uint8_t cmd, wct, andx_cmd;
+
+ cmd = CVAL(buf, smb_com);
+ if (!is_andx_req(cmd)) {
+ return false;
+ }
+ wct = CVAL(buf, smb_wct);
+ if (wct < 2) {
+ return false;
+ }
+ andx_cmd = CVAL(buf, smb_vwv);
+ return (andx_cmd != 0xFF);
+}
+
+bool smb1_walk_chain(const uint8_t *buf,
+ bool (*fn)(uint8_t cmd,
+ uint8_t wct, const uint16_t *vwv,
+ uint16_t num_bytes, const uint8_t *bytes,
+ void *private_data),
+ void *private_data)
+{
+ size_t smblen = smb_len(buf);
+ const char *smb_buf = smb_base(buf);
+ uint8_t cmd, chain_cmd;
+ uint8_t wct;
+ const uint16_t *vwv;
+ uint16_t num_bytes;
+ const uint8_t *bytes;
+
+ cmd = CVAL(buf, smb_com);
+ wct = CVAL(buf, smb_wct);
+ vwv = (const uint16_t *)(buf + smb_vwv);
+ num_bytes = smb_buflen(buf);
+ bytes = (uint8_t *)smb_buf_const(buf);
+
+ if (!fn(cmd, wct, vwv, num_bytes, bytes, private_data)) {
+ return false;
+ }
+
+ if (!is_andx_req(cmd)) {
+ return true;
+ }
+ if (wct < 2) {
+ return false;
+ }
+
+ chain_cmd = CVAL(vwv, 0);
+
+ while (chain_cmd != 0xff) {
+ uint32_t chain_offset; /* uint32_t to avoid overflow */
+ size_t length_needed;
+ ptrdiff_t vwv_offset;
+
+ chain_offset = SVAL(vwv+1, 0);
+
+ /*
+ * Check if the client tries to fool us. The chain
+ * offset needs to point beyond the current request in
+ * the chain, it needs to strictly grow. Otherwise we
+ * might be tricked into an endless loop always
+ * processing the same request over and over again. We
+ * used to assume that vwv and the byte buffer array
+ * in a chain are always attached, but OS/2 the
+ * Write&X/Read&X chain puts the Read&X vwv array
+ * right behind the Write&X vwv chain. The Write&X bcc
+ * array is put behind the Read&X vwv array. So now we
+ * check whether the chain offset points strictly
+ * behind the previous vwv array. req->buf points
+ * right after the vwv array of the previous
+ * request. See
+ * https://bugzilla.samba.org/show_bug.cgi?id=8360 for
+ * more information.
+ */
+
+ vwv_offset = ((const char *)vwv - smb_buf);
+ if (chain_offset <= vwv_offset) {
+ return false;
+ }
+
+ /*
+ * Next check: Make sure the chain offset does not
+ * point beyond the overall smb request length.
+ */
+
+ length_needed = chain_offset+1; /* wct */
+ if (length_needed > smblen) {
+ return false;
+ }
+
+ /*
+ * Now comes the pointer magic. Goal here is to set up
+ * vwv and buf correctly again. The chain offset (the
+ * former vwv[1]) points at the new wct field.
+ */
+
+ wct = CVAL(smb_buf, chain_offset);
+
+ if (is_andx_req(chain_cmd) && (wct < 2)) {
+ return false;
+ }
+
+ /*
+ * Next consistency check: Make the new vwv array fits
+ * in the overall smb request.
+ */
+
+ length_needed += (wct+1)*sizeof(uint16_t); /* vwv+buflen */
+ if (length_needed > smblen) {
+ return false;
+ }
+ vwv = (const uint16_t *)(smb_buf + chain_offset + 1);
+
+ /*
+ * Now grab the new byte buffer....
+ */
+
+ num_bytes = SVAL(vwv+wct, 0);
+
+ /*
+ * .. and check that it fits.
+ */
+
+ length_needed += num_bytes;
+ if (length_needed > smblen) {
+ return false;
+ }
+ bytes = (const uint8_t *)(vwv+wct+1);
+
+ if (!fn(chain_cmd, wct, vwv, num_bytes, bytes, private_data)) {
+ return false;
+ }
+
+ if (!is_andx_req(chain_cmd)) {
+ return true;
+ }
+ chain_cmd = CVAL(vwv, 0);
+ }
+ return true;
+}
+
+static bool smb1_chain_length_cb(uint8_t cmd,
+ uint8_t wct, const uint16_t *vwv,
+ uint16_t num_bytes, const uint8_t *bytes,
+ void *private_data)
+{
+ unsigned *count = (unsigned *)private_data;
+ *count += 1;
+ return true;
+}
+
+unsigned smb1_chain_length(const uint8_t *buf)
+{
+ unsigned count = 0;
+
+ if (!smb1_walk_chain(buf, smb1_chain_length_cb, &count)) {
+ return 0;
+ }
+ return count;
+}
+
+struct smb1_parse_chain_state {
+ TALLOC_CTX *mem_ctx;
+ const uint8_t *buf;
+ struct smbd_server_connection *sconn;
+ bool encrypted;
+ uint32_t seqnum;
+
+ struct smb_request **reqs;
+ unsigned num_reqs;
+};
+
+static bool smb1_parse_chain_cb(uint8_t cmd,
+ uint8_t wct, const uint16_t *vwv,
+ uint16_t num_bytes, const uint8_t *bytes,
+ void *private_data)
+{
+ struct smb1_parse_chain_state *state =
+ (struct smb1_parse_chain_state *)private_data;
+ struct smb_request **reqs;
+ struct smb_request *req;
+ bool ok;
+
+ reqs = talloc_realloc(state->mem_ctx, state->reqs,
+ struct smb_request *, state->num_reqs+1);
+ if (reqs == NULL) {
+ return false;
+ }
+ state->reqs = reqs;
+
+ req = talloc(reqs, struct smb_request);
+ if (req == NULL) {
+ return false;
+ }
+
+ ok = init_smb_request(req, state->sconn, state->buf, 0,
+ state->encrypted, state->seqnum);
+ if (!ok) {
+ return false;
+ }
+ req->cmd = cmd;
+ req->wct = wct;
+ req->vwv = vwv;
+ req->buflen = num_bytes;
+ req->buf = bytes;
+
+ reqs[state->num_reqs] = req;
+ state->num_reqs += 1;
+ return true;
+}
+
+bool smb1_parse_chain(TALLOC_CTX *mem_ctx, const uint8_t *buf,
+ struct smbd_server_connection *sconn,
+ bool encrypted, uint32_t seqnum,
+ struct smb_request ***reqs, unsigned *num_reqs)
+{
+ struct smb1_parse_chain_state state;
+
+ state.mem_ctx = mem_ctx;
+ state.buf = buf;
+ state.sconn = sconn;
+ state.encrypted = encrypted;
+ state.seqnum = seqnum;
+ state.reqs = NULL;
+ state.num_reqs = 0;
+
+ if (!smb1_walk_chain(buf, smb1_parse_chain_cb, &state)) {
+ TALLOC_FREE(state.reqs);
+ return false;
+ }
+ *reqs = state.reqs;
+ *num_reqs = state.num_reqs;
+ return true;
+}
+
/****************************************************************************
Construct a chained reply and add it to the already made reply
****************************************************************************/