From 187eda586023837d103fff7f05d83a7bd42a840a Mon Sep 17 00:00:00 2001 From: leitner Date: Wed, 18 Jan 2023 12:18:01 +0000 Subject: [PATCH] support pipelining, requests > 8k support modify requests on records in data (not the journal) --- Makefile | 6 +- asn1.h | 11 +++ ldap.h | 4 + scan_asn1length.c | 15 ++- scan_ldapmessage_nolengthcheck.c | 8 ++ tinyldap.c | 154 ++++++++++++++++++++++++++++--- 6 files changed, 179 insertions(+), 19 deletions(-) create mode 100644 scan_ldapmessage_nolengthcheck.c diff --git a/Makefile b/Makefile index 3f66ad0..2e20778 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,8 @@ scan_asn1STRING.o scan_asn1SEQUENCE.o scan_asn1ENUMERATED.o \ scan_asn1BOOLEAN.o scan_asn1rawint.o scan_asn1SET.o fmt_asn1sint.o \ fmt_asn1sintpayload.o scan_asn1oid.o scan_asn1BITSTRING.o \ scan_asn1tagint.o fmt_asn1tagint.o fmt_asn1OID.o scan_asn1generic.o \ -fmt_asn1generic.o scan_asn1rawoid.o fmt_asn1bitstring.o asn1oid.o +fmt_asn1generic.o scan_asn1rawoid.o fmt_asn1bitstring.o asn1oid.o \ +scan_asn1SEQUENCE_nolengthcheck.o ldap.a: scan_ldapmessage.o fmt_ldapmessage.o fmt_ldapbindrequest.o \ scan_ldapbindrequest.o scan_ldapbindresponse.o scan_ldapresult.o \ @@ -30,7 +31,8 @@ bstrstart.o free_ldapadl.o free_ldappal.o free_ldapsearchfilter.o \ scan_ldapsearchfilterstring.o free_ldapsearchresultentry.o \ fmt_ldapsearchfilterstring.o ldap_match_sre.o \ fmt_ldapdeleterequest.o scan_ldapdeleterequest.o normalize_dn.o \ -fmt_ldapmodifyrequest.o fmt_ldapaddrequest.o +fmt_ldapmodifyrequest.o fmt_ldapaddrequest.o \ +scan_ldapmessage_nolengthcheck.o ldif.a: ldif_parse.o ldap_match_mapped.o diff --git a/asn1.h b/asn1.h index 0d071c3..fed00c5 100644 --- a/asn1.h +++ b/asn1.h @@ -118,8 +118,14 @@ size_t scan_asn1tag(const char* src,const char* max, enum asn1_tagclass* tc,enum asn1_tagtype* tt, unsigned long* tag); /* parse ASN.1 length */ +/* only return success if source buffer is large enough to hold length bytes */ size_t scan_asn1length(const char* src,const char* max,size_t* length); +/* Same but does not check the source buffer is large enough to hold + * length bytes. Useful to find out how many more bytes we need to read + * from network */ +size_t scan_asn1length_nolengthcheck(const char* src,const char* max, size_t* length); + /* helper for scan_asn1INT, scan_asn1ENUMERATED and scan_asn1BOOLEAN */ size_t scan_asn1int(const char* src,const char* max, enum asn1_tagclass* tc,enum asn1_tagtype* tt, unsigned long* tag, @@ -144,6 +150,11 @@ size_t scan_asn1STRING(const char* src,const char* max,const char** s,size_t* l) size_t scan_asn1BITSTRING(const char* src,const char* max,const char** s,size_t* l); /* note: these only parse the header. src + return value points to first element */ size_t scan_asn1SEQUENCE(const char* src,const char* max,size_t* len); +/* scan_asn1SEQUENCE will only return success if the header and the + * whole contents fit into src..max; this function will only parse the + * outer sequence header and return the number of bytes it say it wants. + * For finding out how much more data you need to read from the socket. */ +size_t scan_asn1SEQUENCE_nolengthcheck(const char* src,const char* max,size_t* len); size_t scan_asn1SET(const char* src,const char* max,size_t* len); /* scan an ASN.1 OID and put the numbers into array. diff --git a/ldap.h b/ldap.h index 96fc5c0..cb35148 100644 --- a/ldap.h +++ b/ldap.h @@ -164,6 +164,10 @@ size_t scan_ldapstring(const char* src,const char* max,struct string* s); size_t scan_ldapmessage(const char* src,const char* max, unsigned long* messageid,unsigned long* op, size_t* len); + +size_t scan_ldapmessage_nolengthcheck(const char* src,const char* max, + unsigned long* messageid,unsigned long* op,size_t* len); + size_t scan_ldapbindrequest(const char* src,const char* max, unsigned long* version,struct string* name, unsigned long* method); diff --git a/scan_asn1length.c b/scan_asn1length.c index 68ce717..95c42be 100644 --- a/scan_asn1length.c +++ b/scan_asn1length.c @@ -1,7 +1,7 @@ #include #include "asn1.h" -size_t scan_asn1length(const char* src,const char* max,size_t* value) { +size_t scan_asn1length_nolengthcheck(const char* src,const char* max,size_t* value) { size_t len=max-src; if (len==0 || len>=-(uintptr_t)src) return 0; unsigned int i,c=*src; @@ -26,12 +26,21 @@ size_t scan_asn1length(const char* src,const char* max,size_t* value) { if (l<0x7f) return 0; /* not minimally encoded: 0x81 0x70 instead of 0x70 */ } - if (l>len-i) - return 0; /* if the length would not fit into the buffer, return 0 */ *value=l; return i; } +size_t scan_asn1length(const char* src,const char* max,size_t* value) { + size_t tmp; + size_t len=scan_asn1length_nolengthcheck(src,max,&tmp); + if (len && (max-src-len >= tmp)) { + *value=tmp; + return len; + } + return 0; +} + + #ifdef UNITTEST #include #include diff --git a/scan_ldapmessage_nolengthcheck.c b/scan_ldapmessage_nolengthcheck.c new file mode 100644 index 0000000..5f9114f --- /dev/null +++ b/scan_ldapmessage_nolengthcheck.c @@ -0,0 +1,8 @@ +#include "ldap.h" + +size_t scan_ldapmessage_nolengthcheck(const char* src,const char* max, + unsigned long* messageid,unsigned long* op,size_t* len) { + size_t res,tmp; + if (!(res=scan_asn1SEQUENCE_nolengthcheck(src,max,len))) return 0; + return res; +} diff --git a/tinyldap.c b/tinyldap.c index 7769dfd..1de129a 100644 --- a/tinyldap.c +++ b/tinyldap.c @@ -73,6 +73,12 @@ uint32 magic,attribute_count,record_count,indices_offset,size_of_string_table; * basic counts and offsets needed to calculate the positions of * the data structures in the file. */ +static uint32* getrecptr(size_t recno) { + if (recno>=record_count) return 0; + uint32_t thisrec = uint32_read(map+indices_offset+4*recno); + return (uint32*)(map+thisrec); +} + /* We do queries with indexes by evaluating all the filters (subexpressions) that can be * answered with an index, and then getting a bit vector, one bit for each record. */ @@ -388,6 +394,7 @@ void map_datafile(const char* filename) { */ #define BUFSIZE 8192 +#define MAXBUFSIZE 1024*1024 #if (debug != 0) /* debugging support functions, adapted from t2.c */ @@ -1185,7 +1192,7 @@ add_attribute: long tmp; if (l<=HUGE_SIZE_FOR_SANITY_CHECKS) { buf=alloca(l+300); /* you never know ;) */ - if (verbose) { + if (debug) { buffer_puts(buffer_2,"sre len "); buffer_putulong(buffer_2,l); buffer_putsflush(buffer_2,".\n"); @@ -1483,6 +1490,30 @@ static int lookupdn(struct string* dn,size_t* index, struct hashnode** hn) { return 0; } +/* return fake hashnode for record from data file. + * all the internal pointers point into the data file, free on the pointer is sufficient + * to clean up everything */ +static struct hashnode* load_record_into_hashnode(size_t recno) { + uint32* attr = getrecptr(recno); + uint32 attrs; + uint32 i; + if (!attr) return 0; + attrs = uint32_read((const char*)attr); + struct hashnode* h = malloc(sizeof(struct hashnode)+attrs*sizeof(struct attribute2)); + if (!h) return 0; + h->next=h->linear=0; + h->hashval=0; + h->dn=(unsigned char*)map+uint32_read((const char*)&(attr[2])); + h->overwrite=1; + h->a[0].a=(unsigned char*)"objectClass"; h->a[0].v=(unsigned char*)map+uint32_read((const char*)&(attr[3])); + h->n=attrs-1; // dn is extra + for (i=2; ia[i-1].a=(unsigned char*)map+uint32_read((const char*)&(attr[i*2])); + h->a[i-1].v=(unsigned char*)map+uint32_read((const char*)&(attr[i*2+1])); + } + return h; +} + static void normalize_string_dn(struct string* s) { /* OK this is a kludge. s->s is supposed to be read-only because it points into the * buffer where we read it into from the network. @@ -1545,21 +1576,105 @@ void reply_with_index(struct SearchRequest* sr,unsigned long* messageid,int out) */ static int handle(int in,int out) { size_t len; - char buf[BUFSIZE]; + char stackbuf[BUFSIZE]; + size_t bufsize=BUFSIZE; + char* buf=stackbuf; for (len=0;;) { - int tmp=read(in,buf+len,BUFSIZE-len); + int tmp; int res; unsigned long messageid,op; size_t Len; - if (tmp==0) { - close(in); - if (in!=out) close(out); - return 0; -// if (BUFSIZE-len) { return 0; } - } - if (tmp<0) { write(2,"error!\n",7); return 1; } - len+=tmp; res=scan_ldapmessage(buf,buf+len,&messageid,&op,&Len); + if (res==0) { + /* Maybe the message is larger than the buffer. Attempt to find out how large the + * buffer should be so we can capture the whole message */ + if (len>0) { + res=scan_ldapmessage_nolengthcheck(buf,buf+len,&messageid,&op,&Len); + if (res) { + /* we could parse the header and have a size. Now check if it is plausible. */ + + if (debug) { + buffer_puts(buffer_2,"got partial message ("); + buffer_putulong(buffer_2,len); + buffer_puts(buffer_2," of "); + buffer_putulong(buffer_2,Len); + buffer_puts(buffer_2," bytes). bufsize is "); + buffer_putulong(buffer_2,bufsize); + buffer_putnlflush(buffer_2); + } + + if (Len > MAXBUFSIZE-100) + outofmemory: + { + /* Peer wants to send us more than MAXBUFSIZE in a message. Abort. */ + char outbuf[1024]; + size_t s=100; + int response; + switch (op) { + case SearchRequest: response=SearchResultDone; break; + case ModifyRequest: response=ModifyResponse; break; + case AddRequest: response=AddResponse; break; + case DelRequest: response=DelResponse; break; + case ModifyDNRequest: response=ModifyDNResponse; break; + case CompareRequest: response=CompareResponse; break; + default: response=BindResponse; + } + size_t len=fmt_ldapresult(outbuf+s,sizeLimitExceeded,"","message too large",""); + size_t hlen=fmt_ldapmessage(0,messageid,response,len); + fmt_ldapmessage(outbuf+s-hlen,messageid,response,len); + write(out,outbuf+s-hlen,len+hlen); + /* This is an attack. We don't continue talking to attackers. */ + /* Also we would have to wastefully read Len bytes here if we wanted to continue. */ + exit(3); + } + /* Peer wants to send more than BUFSIZE bytes, but less than MAXBUFSIZE. */ + bufsize=Len+100; // MAXBUFSIZE should be small enough that adding 100 won't overflow + if (bufsize<100) goto outofmemory; + char* newbuf; + if (buf==stackbuf) { + newbuf=malloc(bufsize); + if (newbuf) byte_copy(newbuf,len,stackbuf); + } else + newbuf=realloc(buf,bufsize); + if (!newbuf) { + if (buf!=stackbuf) free(buf); + goto outofmemory; + } + buf=newbuf; + if (debug) { + buffer_puts(buffer_2,"resized. bufsize now "); + buffer_putulong(buffer_2,bufsize); + buffer_putnlflush(buffer_2); + } + } + } + tmp=read(in,buf+len,bufsize-len); + + if (debug) { + buffer_puts(buffer_2,"read "); + buffer_putlong(buffer_2,tmp); + buffer_puts(buffer_2," bytes at ofs "); + buffer_putulong(buffer_2,len); + buffer_putnlflush(buffer_2); + } + + if (tmp==0) { + close(in); + if (in!=out) close(out); + return 0; + // if (BUFSIZE-len) { return 0; } + } + if (tmp<0) { write(2,"error!\n",7); return 1; } + len+=tmp; + if (debug) { + buffer_puts(buffer_2,"len now "); + buffer_putulong(buffer_2,len); + buffer_putnlflush(buffer_2); + } + + continue; +// res=scan_ldapmessage(buf,buf+len,&messageid,&op,&Len); + } if (res>0) { if (verbose) { buffer_puts(buffer_2,"got message of length "); @@ -1663,7 +1778,7 @@ authfailure: size_t hlen=fmt_ldapmessage(0,messageid,BindResponse,len); fmt_ldapmessage(outbuf+s-hlen,messageid,BindResponse,len); write(out,outbuf+s-hlen,len+hlen); - continue; + break; } } } @@ -1807,7 +1922,16 @@ authfailure: if (err==success) { #if 1 /* 3. apply modifications to record to get new record */ - if (!applymodreq(hn,&mr,&sre)) { + struct hashnode* h; + if (hn) + h=hn; + else + h=load_record_into_hashnode(idx); + if (!h) { + err=operationsError; // can't happen + goto modreqerror; + } + if (!applymodreq(h,&mr,&sre)) { /* 4. write record to journal */ int fd=open(journalfilename,O_WRONLY|O_APPEND|O_CREAT,0600); if (fd==-1) @@ -1819,6 +1943,8 @@ authfailure: } } else err=operationsError; + if (h != hn) free(h); +modreqerror: free_ldapsearchresultentry(&sre); #else err=operationsError; @@ -1933,7 +2059,7 @@ authfailure: if (checkacl(0,0,acl_delete,&sre)!=1) err=insufficientAccessRights; if (err==success) { - /* 2. check if there already is a record with this dn */ + /* 2. check if there is a record with this dn */ struct hashnode* hn; size_t idx; switch (lookupdn(&s,&idx,&hn)) {