NTDOM: dce/rpc long data transfers - memory handling

Luke Kenneth Casson Leighton lkcl at regent.push.net
Sun Apr 12 17:50:44 GMT 1998


i decided to post this on a couple of newsgroups.  for the benefit of
those groups, i will describe some of the background.  the samba team
(samba-bugs at samba.anu.edu.au> are making all versions of UNIX look like
Windows NT Server.  this is being achieved by network-reverse-engineering
NT using packet tracing, and also with some valuable assistance and input
from NT administrators and developers.

what is needed is to develop a microsoft-specific implementation of
DCE/RPC.  this is done using SMBtrans, SMBreadX and SMBwriteX.  the
client-side is working; the server-side is a mess (a hack) and isn't
working.  i need to move beyond the prototype stage (written between oct
97 and jan 98).

my questions are:

- does anyone have any GPL code which handles memory blocks in the way
described below?  if so, please let me know its location.

- does anyone else want to be involved in making UNIX look like Windows NT
server?  it's not all quite as terrible as this below [and i'm a good
liar].  if so, i recommend joining the samba-technical or samba-ntdom
lists, on which the development / administration issues will be discussed
(see references below).  in the samba-ntdom archives, there is a TODO List
thread.


ok.  this is for notes / informational purposes: it helps me out to yap
about what i need to do, and clarify what is / isn't working.

i assume (probably incorrectly) that nt has some data structures,
internally, where you can concatenate blocks of data together in a list,
and they can be treated as a contiguous stream (similar to the structure
used by writev()).  this allows you to create a series of headers and some
data, but you don't have to do any silly reallocs / memory moves and
such-like in order to insert a block of memory into the "middle" of
another block.

so, you have a data stream (28954 bytes).  you want to send headers of 24
bytes every 5260 bytes.  so you've generated your data stream, and now you
do a memmove at the start, then at 5260 bytes, then at 5260*2-24, ... 
right?  wrong. 

you create three data structures, like this: 

struct mem_block
{
	char *allocated_data;
	uint32 len;

	struct mem_block *next;
};

struct mem_list
{
	uint32 start;
	uint32 len;
	char *data_ptr;

	struct mem_list *next;
};

struct compound_mem
{
	struct mem_block *mem_array;
	struct mem_list *mem_description;
};

mem_block is the actual memory; mem_list describes the way that that
memory is strung together, linearly.

mem_list contains pointers into a mem_block's allocated_data.  mem_list's
start / len members are strictly speaking not necessary: you could just
have "len", and it would probably be a good idea to only have "len".  the
overhead of having to calculate the offset is counterbalanced by the risk
of the "start" member not being understood / set correctly. 



so.  once you have these data structures, you can move on to getting them
over-the-wire.  this is *not* trivial.

1) dce/rpc data is split up into (0x1630 - 0x18) byte "fragments",
interspersed with 0x18 bytes of header.  (actually, a fragment _includes_
the header).  the last fragment (including the header) obviously may be
less than 0x1630 bytes.

0x18 header + 0x1618 data = frag of 0x1630
0x18 header + 0x1618 data = frag of 0x1630
0x18 header + 0x03e8 data = frag 0f 0x0400

2) client sends an SMBtrans request.  the dce/rpc header is sent,
concatenated with the data.  SMBtrans requests, however, max out at a
negotiated size (usually 1024 bytes).  i have not studied what happens,
here, so will skip this for now.

moving on to the response, the SMBtrans response also maxes out at a
negotiated size (usually 1024 bytes) if the dce/rpc data/header
combinations left to be sent are more than this, then the SMBtrans
response is marked with a "STATUS_BUFFER_OVERFLOW" warning, and only the
first 1024 bytes sent.

3) an SMBreadX is sent by the client, starting at an offset of 0x0 and
requesting the rest of the current fragment: NO more.  the SMBtrans
response will have contained a dce/rpc header, and the fragment length
will have been obtained from this.  the server responds with the rest of
the fragment.

if the first fragment is being requested, then the data already sent in
the SMBtrans request is _not_ sent a second time, despite the offset in
the SMBreadX being 0x0.  [this only makes sense if you appreciate that the
SMBtrans response was likely to have been split down already into
fragments, using a memory concatenation structure described above.  one
set of those fragments is 0x18 bytes plus 1024-0x18 bytes bytes in length: 
the dce/rpc header plus dce/rpc data].

if the maximum size that an SMBreadX can contain is less than the dce/rpc
fragment length, then multiple SMBreadXs are sent.  if the maximum data
size an SMBreadX can do is 1024 bytes, then the first SMBreadX will be at
offset 0x0, length 0x400; the second SMBreadX at offset 0x400, length
0x400 etc, until the current dce/rpc fragment is read.

note that the offset starts at 0x0 and increments until the end of a
fragment is reached. once an entire fragment is read, the offset is reset
to 0x0 again.

repeat until the last fragment is reached.

i thought, fine, fine.  implement this, off we go, simple.  no.  not at
all.

4) smbclient can do dce/rpc client calls.  for simplicity, i first do an
SMBtrans, and then if necessary, a set of SMBreadX calls to complete the
reading of the first fragment, starting at an SMBreadX offset of 0x0 and
incrementing, as described above.

for simplicity, i read 0x18 bytes of dce/rpc header (decode the header,
get the frag_len), followed by frag_len - 0x18 bytes of data.  BY MISTAKE,
the SMBreadX call for both the 0x18 call and the first of the frag_len -
0x18 calls i make at an offset of 0x0... AND IT WORKS! 

so you have this:

first frag (dce/rpc fragment lengths in the header are 0x1630 bytes) -

SMBtrans response (1024 bytes, which is header plus 1024-0x18 data)
SMBreadX offset 0x0 length 0x1630 - 1024 = 0x1230 bytes

second frag -

SMBreadX offset 0x0 length 0x18 (dce/rpc header)
SMBreadX offset 0x0 length 0x1630-0x18 (dce/rpc data)


i _expect_ the last-mentioned SMBreadX to be:

SMBreadX offset 0x18 length 0x1630-0x18 (dce/rpc data)



in other words... imagine that you have a compound memory structure (as
described above), consisting of:

SMBtrans response for 1st dce/rpc frag:

	mem_block nnnn (SMBtrans response header)
	mem_block 0x18 (dce/rpc header)
	mem_block 1024-0x18 (dce/rpc data)

SMBreadX response for 1st dce/rpc frag:

	mem_block nnnn (SMBreadX response header)
	mem_block 0x1630-1024-0x18 (dce/rpc data)

SMBreadX response for 2nd dce/rpc frag:

	mem_block nnnn (SMBreadX response header)
	mem_block 0x18 (dce/rpc header)
	mem_block 0x1630-0x18 (dce/rpc data)

you can see that each mem_block (dce/rpc header or data) is handled
individually when you do an SMBreadX: the SMBreadX offset must be from the
start of the currently outstanding mem_block.


there is a small extension of this.  packet traces of NT/NT interaction
show the second frag to be: 

SMBreadX offset 0x0 length 0x1630 (dce/rpc header plus data)

this could be indicative that if your SMBreadX spans two mem_blocks, you
include both of them: in this case, a single dce/rpc header plus the data
block it refers to.



of course, this could just be, as terry pratchett says, so much marsh gas.
thank you for putting up with such a terrible posting: hope you can help.

luke (samba team)

web references:

	http://samba.anu.edu.au/pub/samba
	http://samba.anu.edu.au/cgi-bin/cvsweb/samba
	http://samba.anu.edu.au/cvs.html
	http://samba.anu.edu.au/listproc
	http://samba.anu.edu.au/listproc/samba-technical
	http://samba.anu.edu.au/listproc/samba-ntdom
	http://www.cb1.com/~lkcl
	http://discuss.microsoft.com - cifs list




More information about the samba-technical mailing list