1
0
mirror of https://github.com/samba-team/samba.git synced 2024-12-25 23:21:54 +03:00
samba-mirror/source4/libcli/smb2/request.c
Andrew Tridgell 26b8701321 handle pushing of zero length smb2 strings
(This used to be commit 66d0502228)
2008-02-18 14:53:48 +11:00

706 lines
18 KiB
C

/*
Unix SMB/CIFS implementation.
SMB2 client request handling
Copyright (C) Andrew Tridgell 2005
Copyright (C) Stefan Metzmacher 2005
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "includes.h"
#include "libcli/raw/libcliraw.h"
#include "libcli/smb2/smb2.h"
#include "lib/util/dlinklist.h"
#include "lib/events/events.h"
#include "libcli/smb2/smb2_calls.h"
#include "param/param.h"
/* fill in the bufinfo */
void smb2_setup_bufinfo(struct smb2_request *req)
{
req->in.bufinfo.mem_ctx = req;
req->in.bufinfo.flags = BUFINFO_FLAG_UNICODE | BUFINFO_FLAG_SMB2;
req->in.bufinfo.align_base = req->in.buffer;
if (req->in.dynamic) {
req->in.bufinfo.data = req->in.dynamic;
req->in.bufinfo.data_size = req->in.body_size - req->in.body_fixed;
} else {
req->in.bufinfo.data = NULL;
req->in.bufinfo.data_size = 0;
}
}
/*
initialise a smb2 request
*/
struct smb2_request *smb2_request_init(struct smb2_transport *transport, uint16_t opcode,
uint16_t body_fixed_size, bool body_dynamic_present,
uint32_t body_dynamic_size)
{
struct smb2_request *req;
uint64_t seqnum;
if (body_dynamic_present) {
if (body_dynamic_size == 0) {
body_dynamic_size = 1;
}
} else {
body_dynamic_size = 0;
}
req = talloc(transport, struct smb2_request);
if (req == NULL) return NULL;
seqnum = transport->seqnum++;
if (seqnum == UINT64_MAX) {
seqnum = transport->seqnum++;
}
req->state = SMB2_REQUEST_INIT;
req->transport = transport;
req->session = NULL;
req->tree = NULL;
req->seqnum = seqnum;
req->status = NT_STATUS_OK;
req->async.fn = NULL;
req->next = req->prev = NULL;
ZERO_STRUCT(req->cancel);
ZERO_STRUCT(req->in);
req->out.size = SMB2_HDR_BODY+NBT_HDR_SIZE+body_fixed_size;
req->out.allocated = req->out.size + body_dynamic_size;
req->out.buffer = talloc_array(req, uint8_t, req->out.allocated);
if (req->out.buffer == NULL) {
talloc_free(req);
return NULL;
}
req->out.hdr = req->out.buffer + NBT_HDR_SIZE;
req->out.body = req->out.hdr + SMB2_HDR_BODY;
req->out.body_fixed= body_fixed_size;
req->out.body_size = body_fixed_size;
req->out.dynamic = (body_dynamic_size ? req->out.body + body_fixed_size : NULL);
SIVAL(req->out.hdr, 0, SMB2_MAGIC);
SSVAL(req->out.hdr, SMB2_HDR_LENGTH, SMB2_HDR_BODY);
SSVAL(req->out.hdr, SMB2_HDR_EPOCH, 0);
SIVAL(req->out.hdr, SMB2_HDR_STATUS, 0);
SSVAL(req->out.hdr, SMB2_HDR_OPCODE, opcode);
SSVAL(req->out.hdr, SMB2_HDR_CREDIT, 0);
SIVAL(req->out.hdr, SMB2_HDR_FLAGS, 0);
SIVAL(req->out.hdr, SMB2_HDR_NEXT_COMMAND, 0);
SBVAL(req->out.hdr, SMB2_HDR_MESSAGE_ID, req->seqnum);
SIVAL(req->out.hdr, SMB2_HDR_PID, 0);
SIVAL(req->out.hdr, SMB2_HDR_TID, 0);
SBVAL(req->out.hdr, SMB2_HDR_SESSION_ID, 0);
memset(req->out.hdr+SMB2_HDR_SIGNATURE, 0, 16);
/* set the length of the fixed body part and +1 if there's a dynamic part also */
SSVAL(req->out.body, 0, body_fixed_size + (body_dynamic_size?1:0));
/*
* if we have a dynamic part, make sure the first byte
* which is always be part of the packet is initialized
*/
if (body_dynamic_size) {
req->out.size += 1;
SCVAL(req->out.dynamic, 0, 0);
}
return req;
}
/*
initialise a smb2 request for tree operations
*/
struct smb2_request *smb2_request_init_tree(struct smb2_tree *tree, uint16_t opcode,
uint16_t body_fixed_size, bool body_dynamic_present,
uint32_t body_dynamic_size)
{
struct smb2_request *req = smb2_request_init(tree->session->transport, opcode,
body_fixed_size, body_dynamic_present,
body_dynamic_size);
if (req == NULL) return NULL;
SBVAL(req->out.hdr, SMB2_HDR_SESSION_ID, tree->session->uid);
SIVAL(req->out.hdr, SMB2_HDR_TID, tree->tid);
req->session = tree->session;
req->tree = tree;
return req;
}
/* destroy a request structure and return final status */
NTSTATUS smb2_request_destroy(struct smb2_request *req)
{
NTSTATUS status;
/* this is the error code we give the application for when a
_send() call fails completely */
if (!req) return NT_STATUS_UNSUCCESSFUL;
if (req->transport) {
/* remove it from the list of pending requests (a null op if
its not in the list) */
DLIST_REMOVE(req->transport->pending_recv, req);
}
if (req->state == SMB2_REQUEST_ERROR &&
NT_STATUS_IS_OK(req->status)) {
req->status = NT_STATUS_INTERNAL_ERROR;
}
status = req->status;
talloc_free(req);
return status;
}
/*
receive a response to a packet
*/
bool smb2_request_receive(struct smb2_request *req)
{
/* req can be NULL when a send has failed. This eliminates lots of NULL
checks in each module */
if (!req) return false;
/* keep receiving packets until this one is replied to */
while (req->state <= SMB2_REQUEST_RECV) {
if (event_loop_once(req->transport->socket->event.ctx) != 0) {
return false;
}
}
return req->state == SMB2_REQUEST_DONE;
}
/* Return true if the last packet was in error */
bool smb2_request_is_error(struct smb2_request *req)
{
return NT_STATUS_IS_ERR(req->status);
}
/* Return true if the last packet was OK */
bool smb2_request_is_ok(struct smb2_request *req)
{
return NT_STATUS_IS_OK(req->status);
}
/*
check if a range in the reply body is out of bounds
*/
bool smb2_oob(struct smb2_request_buffer *buf, const uint8_t *ptr, size_t size)
{
if (size == 0) {
/* zero bytes is never out of range */
return false;
}
/* be careful with wraparound! */
if (ptr < buf->body ||
ptr >= buf->body + buf->body_size ||
size > buf->body_size ||
ptr + size > buf->body + buf->body_size) {
return true;
}
return false;
}
size_t smb2_padding_size(uint32_t offset, size_t n)
{
if ((offset & (n-1)) == 0) return 0;
return n - (offset & (n-1));
}
static size_t smb2_padding_fix(struct smb2_request_buffer *buf)
{
if (buf->dynamic == (buf->body + buf->body_fixed)) {
return 1;
}
return 0;
}
/*
grow a SMB2 buffer by the specified amount
*/
static NTSTATUS smb2_grow_buffer(struct smb2_request_buffer *buf, size_t increase)
{
size_t dynamic_ofs;
uint8_t *buffer_ptr;
uint32_t newsize = buf->size + increase;
/* a packet size should be limited a bit */
if (newsize >= 0x00FFFFFF) return NT_STATUS_MARSHALL_OVERFLOW;
if (newsize <= buf->allocated) return NT_STATUS_OK;
dynamic_ofs = buf->dynamic - buf->buffer;
buffer_ptr = talloc_realloc(buf, buf->buffer, uint8_t, newsize);
NT_STATUS_HAVE_NO_MEMORY(buffer_ptr);
buf->buffer = buffer_ptr;
buf->hdr = buf->buffer + NBT_HDR_SIZE;
buf->body = buf->hdr + SMB2_HDR_BODY;
buf->dynamic = buf->buffer + dynamic_ofs;
buf->allocated = newsize;
return NT_STATUS_OK;
}
/*
pull a uint16_t ofs/ uint16_t length/blob triple from a data blob
the ptr points to the start of the offset/length pair
*/
NTSTATUS smb2_pull_o16s16_blob(struct smb2_request_buffer *buf, TALLOC_CTX *mem_ctx, uint8_t *ptr, DATA_BLOB *blob)
{
uint16_t ofs, size;
if (smb2_oob(buf, ptr, 4)) {
return NT_STATUS_BUFFER_TOO_SMALL;
}
ofs = SVAL(ptr, 0);
size = SVAL(ptr, 2);
if (ofs == 0) {
*blob = data_blob(NULL, 0);
return NT_STATUS_OK;
}
if (smb2_oob(buf, buf->hdr + ofs, size)) {
return NT_STATUS_BUFFER_TOO_SMALL;
}
*blob = data_blob_talloc(mem_ctx, buf->hdr + ofs, size);
NT_STATUS_HAVE_NO_MEMORY(blob->data);
return NT_STATUS_OK;
}
/*
push a uint16_t ofs/ uint16_t length/blob triple into a data blob
the ofs points to the start of the offset/length pair, and is relative
to the body start
*/
NTSTATUS smb2_push_o16s16_blob(struct smb2_request_buffer *buf,
uint16_t ofs, DATA_BLOB blob)
{
NTSTATUS status;
size_t offset;
size_t padding_length;
size_t padding_fix;
uint8_t *ptr = buf->body+ofs;
if (buf->dynamic == NULL) {
return NT_STATUS_INVALID_PARAMETER;
}
/* we have only 16 bit for the size */
if (blob.length > 0xFFFF) {
return NT_STATUS_BUFFER_TOO_SMALL;
}
/* check if there're enough room for ofs and size */
if (smb2_oob(buf, ptr, 4)) {
return NT_STATUS_BUFFER_TOO_SMALL;
}
if (blob.data == NULL) {
if (blob.length != 0) {
return NT_STATUS_INTERNAL_ERROR;
}
SSVAL(ptr, 0, 0);
SSVAL(ptr, 2, 0);
return NT_STATUS_OK;
}
offset = buf->dynamic - buf->hdr;
padding_length = smb2_padding_size(offset, 2);
offset += padding_length;
padding_fix = smb2_padding_fix(buf);
SSVAL(ptr, 0, offset);
SSVAL(ptr, 2, blob.length);
status = smb2_grow_buffer(buf, blob.length + padding_length - padding_fix);
NT_STATUS_NOT_OK_RETURN(status);
memset(buf->dynamic, 0, padding_length);
buf->dynamic += padding_length;
memcpy(buf->dynamic, blob.data, blob.length);
buf->dynamic += blob.length;
buf->size += blob.length + padding_length - padding_fix;
buf->body_size += blob.length + padding_length;
return NT_STATUS_OK;
}
/*
push a uint16_t ofs/ uint32_t length/blob triple into a data blob
the ofs points to the start of the offset/length pair, and is relative
to the body start
*/
NTSTATUS smb2_push_o16s32_blob(struct smb2_request_buffer *buf,
uint16_t ofs, DATA_BLOB blob)
{
NTSTATUS status;
size_t offset;
size_t padding_length;
size_t padding_fix;
uint8_t *ptr = buf->body+ofs;
if (buf->dynamic == NULL) {
return NT_STATUS_INVALID_PARAMETER;
}
/* check if there're enough room for ofs and size */
if (smb2_oob(buf, ptr, 6)) {
return NT_STATUS_BUFFER_TOO_SMALL;
}
if (blob.data == NULL) {
if (blob.length != 0) {
return NT_STATUS_INTERNAL_ERROR;
}
SSVAL(ptr, 0, 0);
SIVAL(ptr, 2, 0);
return NT_STATUS_OK;
}
offset = buf->dynamic - buf->hdr;
padding_length = smb2_padding_size(offset, 2);
offset += padding_length;
padding_fix = smb2_padding_fix(buf);
SSVAL(ptr, 0, offset);
SIVAL(ptr, 2, blob.length);
status = smb2_grow_buffer(buf, blob.length + padding_length - padding_fix);
NT_STATUS_NOT_OK_RETURN(status);
memset(buf->dynamic, 0, padding_length);
buf->dynamic += padding_length;
memcpy(buf->dynamic, blob.data, blob.length);
buf->dynamic += blob.length;
buf->size += blob.length + padding_length - padding_fix;
buf->body_size += blob.length + padding_length;
return NT_STATUS_OK;
}
/*
push a uint32_t ofs/ uint32_t length/blob triple into a data blob
the ofs points to the start of the offset/length pair, and is relative
to the body start
*/
NTSTATUS smb2_push_o32s32_blob(struct smb2_request_buffer *buf,
uint32_t ofs, DATA_BLOB blob)
{
NTSTATUS status;
size_t offset;
size_t padding_length;
size_t padding_fix;
uint8_t *ptr = buf->body+ofs;
if (buf->dynamic == NULL) {
return NT_STATUS_INVALID_PARAMETER;
}
/* check if there're enough room for ofs and size */
if (smb2_oob(buf, ptr, 8)) {
return NT_STATUS_BUFFER_TOO_SMALL;
}
if (blob.data == NULL) {
if (blob.length != 0) {
return NT_STATUS_INTERNAL_ERROR;
}
SIVAL(ptr, 0, 0);
SIVAL(ptr, 4, 0);
return NT_STATUS_OK;
}
offset = buf->dynamic - buf->hdr;
padding_length = smb2_padding_size(offset, 8);
offset += padding_length;
padding_fix = smb2_padding_fix(buf);
SIVAL(ptr, 0, offset);
SIVAL(ptr, 4, blob.length);
status = smb2_grow_buffer(buf, blob.length + padding_length - padding_fix);
NT_STATUS_NOT_OK_RETURN(status);
memset(buf->dynamic, 0, padding_length);
buf->dynamic += padding_length;
memcpy(buf->dynamic, blob.data, blob.length);
buf->dynamic += blob.length;
buf->size += blob.length + padding_length - padding_fix;
buf->body_size += blob.length + padding_length;
return NT_STATUS_OK;
}
/*
push a uint32_t length/ uint32_t ofs/blob triple into a data blob
the ofs points to the start of the length/offset pair, and is relative
to the body start
*/
NTSTATUS smb2_push_s32o32_blob(struct smb2_request_buffer *buf,
uint32_t ofs, DATA_BLOB blob)
{
NTSTATUS status;
size_t offset;
size_t padding_length;
size_t padding_fix;
uint8_t *ptr = buf->body+ofs;
if (buf->dynamic == NULL) {
return NT_STATUS_INVALID_PARAMETER;
}
/* check if there're enough room for ofs and size */
if (smb2_oob(buf, ptr, 8)) {
return NT_STATUS_BUFFER_TOO_SMALL;
}
if (blob.data == NULL) {
if (blob.length != 0) {
return NT_STATUS_INTERNAL_ERROR;
}
SIVAL(ptr, 0, 0);
SIVAL(ptr, 4, 0);
return NT_STATUS_OK;
}
offset = buf->dynamic - buf->hdr;
padding_length = smb2_padding_size(offset, 8);
offset += padding_length;
padding_fix = smb2_padding_fix(buf);
SIVAL(ptr, 0, blob.length);
SIVAL(ptr, 4, offset);
status = smb2_grow_buffer(buf, blob.length + padding_length - padding_fix);
NT_STATUS_NOT_OK_RETURN(status);
memset(buf->dynamic, 0, padding_length);
buf->dynamic += padding_length;
memcpy(buf->dynamic, blob.data, blob.length);
buf->dynamic += blob.length;
buf->size += blob.length + padding_length - padding_fix;
buf->body_size += blob.length + padding_length;
return NT_STATUS_OK;
}
/*
pull a uint16_t ofs/ uint32_t length/blob triple from a data blob
the ptr points to the start of the offset/length pair
*/
NTSTATUS smb2_pull_o16s32_blob(struct smb2_request_buffer *buf, TALLOC_CTX *mem_ctx, uint8_t *ptr, DATA_BLOB *blob)
{
uint16_t ofs;
uint32_t size;
if (smb2_oob(buf, ptr, 6)) {
return NT_STATUS_BUFFER_TOO_SMALL;
}
ofs = SVAL(ptr, 0);
size = IVAL(ptr, 2);
if (ofs == 0) {
*blob = data_blob(NULL, 0);
return NT_STATUS_OK;
}
if (smb2_oob(buf, buf->hdr + ofs, size)) {
return NT_STATUS_BUFFER_TOO_SMALL;
}
*blob = data_blob_talloc(mem_ctx, buf->hdr + ofs, size);
NT_STATUS_HAVE_NO_MEMORY(blob->data);
return NT_STATUS_OK;
}
/*
pull a uint32_t ofs/ uint32_t length/blob triple from a data blob
the ptr points to the start of the offset/length pair
*/
NTSTATUS smb2_pull_o32s32_blob(struct smb2_request_buffer *buf, TALLOC_CTX *mem_ctx, uint8_t *ptr, DATA_BLOB *blob)
{
uint32_t ofs, size;
if (smb2_oob(buf, ptr, 8)) {
return NT_STATUS_BUFFER_TOO_SMALL;
}
ofs = IVAL(ptr, 0);
size = IVAL(ptr, 4);
if (ofs == 0) {
*blob = data_blob(NULL, 0);
return NT_STATUS_OK;
}
if (smb2_oob(buf, buf->hdr + ofs, size)) {
return NT_STATUS_BUFFER_TOO_SMALL;
}
*blob = data_blob_talloc(mem_ctx, buf->hdr + ofs, size);
NT_STATUS_HAVE_NO_MEMORY(blob->data);
return NT_STATUS_OK;
}
/*
pull a uint16_t ofs/ uint32_t length/blob triple from a data blob
the ptr points to the start of the offset/length pair
In this varient the uint16_t is padded by an extra 2 bytes, making
the size aligned on 4 byte boundary
*/
NTSTATUS smb2_pull_o16As32_blob(struct smb2_request_buffer *buf, TALLOC_CTX *mem_ctx, uint8_t *ptr, DATA_BLOB *blob)
{
uint32_t ofs, size;
if (smb2_oob(buf, ptr, 8)) {
return NT_STATUS_BUFFER_TOO_SMALL;
}
ofs = SVAL(ptr, 0);
size = IVAL(ptr, 4);
if (ofs == 0) {
*blob = data_blob(NULL, 0);
return NT_STATUS_OK;
}
if (smb2_oob(buf, buf->hdr + ofs, size)) {
return NT_STATUS_BUFFER_TOO_SMALL;
}
*blob = data_blob_talloc(mem_ctx, buf->hdr + ofs, size);
NT_STATUS_HAVE_NO_MEMORY(blob->data);
return NT_STATUS_OK;
}
/*
pull a uint32_t length/ uint32_t ofs/blob triple from a data blob
the ptr points to the start of the offset/length pair
*/
NTSTATUS smb2_pull_s32o32_blob(struct smb2_request_buffer *buf, TALLOC_CTX *mem_ctx, uint8_t *ptr, DATA_BLOB *blob)
{
uint32_t ofs, size;
if (smb2_oob(buf, ptr, 8)) {
return NT_STATUS_BUFFER_TOO_SMALL;
}
size = IVAL(ptr, 0);
ofs = IVAL(ptr, 4);
if (ofs == 0) {
*blob = data_blob(NULL, 0);
return NT_STATUS_OK;
}
if (smb2_oob(buf, buf->hdr + ofs, size)) {
return NT_STATUS_BUFFER_TOO_SMALL;
}
*blob = data_blob_talloc(mem_ctx, buf->hdr + ofs, size);
NT_STATUS_HAVE_NO_MEMORY(blob->data);
return NT_STATUS_OK;
}
/*
pull a string in a uint16_t ofs/ uint16_t length/blob format
UTF-16 without termination
*/
NTSTATUS smb2_pull_o16s16_string(struct smb2_request_buffer *buf, TALLOC_CTX *mem_ctx,
uint8_t *ptr, const char **str)
{
DATA_BLOB blob;
NTSTATUS status;
ssize_t size;
void *vstr;
status = smb2_pull_o16s16_blob(buf, mem_ctx, ptr, &blob);
NT_STATUS_NOT_OK_RETURN(status);
if (blob.data == NULL) {
*str = NULL;
return NT_STATUS_OK;
}
if (blob.length == 0) {
char *s;
s = talloc_strdup(mem_ctx, "");
NT_STATUS_HAVE_NO_MEMORY(s);
*str = s;
return NT_STATUS_OK;
}
size = convert_string_talloc(mem_ctx, lp_iconv_convenience(global_loadparm), CH_UTF16, CH_UNIX,
blob.data, blob.length, &vstr);
data_blob_free(&blob);
(*str) = (char *)vstr;
if (size == -1) {
return NT_STATUS_ILLEGAL_CHARACTER;
}
return NT_STATUS_OK;
}
/*
push a string in a uint16_t ofs/ uint16_t length/blob format
UTF-16 without termination
*/
NTSTATUS smb2_push_o16s16_string(struct smb2_request_buffer *buf,
uint16_t ofs, const char *str)
{
DATA_BLOB blob;
NTSTATUS status;
ssize_t size;
if (str == NULL) {
return smb2_push_o16s16_blob(buf, ofs, data_blob(NULL, 0));
}
if (*str == 0) {
blob.data = str;
blob.length = 0;
return smb2_push_o16s16_blob(buf, ofs, blob);
}
size = convert_string_talloc(buf->buffer, lp_iconv_convenience(global_loadparm), CH_UNIX, CH_UTF16,
str, strlen(str), (void **)&blob.data);
if (size == -1) {
return NT_STATUS_ILLEGAL_CHARACTER;
}
blob.length = size;
status = smb2_push_o16s16_blob(buf, ofs, blob);
data_blob_free(&blob);
return status;
}
/*
push a file handle into a buffer
*/
void smb2_push_handle(uint8_t *data, struct smb2_handle *h)
{
SBVAL(data, 0, h->data[0]);
SBVAL(data, 8, h->data[1]);
}
/*
pull a file handle from a buffer
*/
void smb2_pull_handle(uint8_t *ptr, struct smb2_handle *h)
{
h->data[0] = BVAL(ptr, 0);
h->data[1] = BVAL(ptr, 8);
}