Bug 14239 - [FUZZ] nmblib depending on uninitialised values
Summary: [FUZZ] nmblib depending on uninitialised values
Status: RESOLVED FIXED
Alias: None
Product: Samba 4.1 and newer
Classification: Unclassified
Component: File services (show other bugs)
Version: unspecified
Hardware: All All
: P5 normal (vote)
Target Milestone: ---
Assignee: Karolin Seeger
QA Contact: Samba QA Contact
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2020-01-16 23:55 UTC by Douglas Bagnall
Modified: 2026-07-29 09:05 UTC (History)
1 user (show)

See Also:


Attachments
[patch] this might be all we need (827 bytes, patch)
2020-01-17 00:08 UTC, Douglas Bagnall
no flags Details
raw test patch. (566 bytes, patch)
2020-01-17 01:03 UTC, Jeremy Allison
no flags Details
git-am fix that went into master (1.53 KB, patch)
2020-01-23 00:51 UTC, Jeremy Allison
dbagnall: review+
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Douglas Bagnall 2020-01-16 23:55:28 UTC
There are two related oss-fuzz findings:

https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=20156
https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=20157

which both point to problems like this:

==1==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000004b9f at pc 0x55eda0837148 bp 0x7ffe312ce970 sp 0x7ffe312ce968
	READ of size 1 at 0x602000004b9f thread T0
	SCARINESS: 12 (1-byte-read-heap-buffer-overflow)
	    #0 0x55eda0837147 in parse_nmb_name samba/source3/libsmb/nmblib.c:208:8
	    #1 0x55eda083728c in parse_alloc_res_rec samba/source3/libsmb/nmblib.c:404:11
	    #2 0x55eda0832299 in parse_nmb samba/source3/libsmb/nmblib.c:591:5
	    #3 0x55eda0831b85 in parse_packet samba/source3/libsmb/nmblib.c:797:8
	    #4 0x55eda096a7f0 in LLVMFuzzerTestOneInput samba/lib/fuzzing/fuzz_nmblib_parse_packet.c:36:6


Valgrind has it as dependencies on uninitialized values:

==25774== Conditional jump or move depends on uninitialised value(s)
==25774==    at 0x130C6D: hfuzz_trace_cmp4_internal (in /home/douglasb/src/samba/bin/default/lib/fuzzing/fuzz_nmblib_parse_packet)
==25774==    by 0x130CEC: __sanitizer_cov_trace_cmp4 (in /home/douglasb/src/samba/bin/default/lib/fuzzing/fuzz_nmblib_parse_packet)
==25774==    by 0xA5E3C04: parse_nmb_name (nmblib.c:210)
==25774==    by 0xA5DF87C: parse_nmb (nmblib.c:574)
==25774==    by 0xA5DF285: parse_packet (nmblib.c:797)
==25774==    by 0x13056D: LLVMFuzzerTestOneInput (fuzz_nmblib_parse_packet.c:36)
[...]
==25774== Conditional jump or move depends on uninitialised value(s)
==25774==    at 0xA5E3C0E: parse_nmb_name (nmblib.c:210)
==25774==    by 0xA5DF87C: parse_nmb (nmblib.c:574)
==25774==    by 0xA5DF285: parse_packet (nmblib.c:797)
==25774==    by 0x13056D: LLVMFuzzerTestOneInput (fuzz_nmblib_parse_packet.c:36)

and

==25375== Conditional jump or move depends on uninitialised value(s)
==25375==    at 0x130C6D: hfuzz_trace_cmp4_internal (in /home/douglasb/src/samba/bin/default/lib/fuzzing/fuzz_nmblib_parse_packet)
==25375==    by 0x130CEC: __sanitizer_cov_trace_cmp4 (in /home/douglasb/src/samba/bin/default/lib/fuzzing/fuzz_nmblib_parse_packet)
==25375==    by 0xA5E3C04: parse_nmb_name (nmblib.c:210)
==25375==    by 0xA5E42C8: parse_alloc_res_rec (nmblib.c:404)
==25375==    by 0xA5DFA8B: parse_nmb (nmblib.c:591)
==25375==    by 0xA5DF285: parse_packet (nmblib.c:797)
==25375==    by 0x13056D: LLVMFuzzerTestOneInput (fuzz_nmblib_parse_packet.c:36)
[...]
==25375== 
==25375== Conditional jump or move depends on uninitialised value(s)
==25375==    at 0xA5E3C0E: parse_nmb_name (nmblib.c:210)
==25375==    by 0xA5E42C8: parse_alloc_res_rec (nmblib.c:404)
==25375==    by 0xA5DFA8B: parse_nmb (nmblib.c:591)
==25375==    by 0xA5DF285: parse_packet (nmblib.c:797)
==25375==    by 0x13056D: LLVMFuzzerTestOneInput (fuzz_nmblib_parse_packet.c:36)


The minimised strings are:

clusterfuzz-testcase-minimized-fuzz_nmblib_parse_packet-5644827043823616
00000000  20 20 20 20 20 20 20 20  20 20 20 20 01 41 49     |            .AI|
0000000f

clusterfuzz-testcase-minimized-fuzz_nmblib_parse_packet-5108333216530432
00000000  00 00 81 00 00 00 4f 4f  01 4f 4f 4f 01 4f 4f     |......OO.OOO.OO|
0000000f


Just looking at

        offset++;                                                                                                              
        while (m > 0) {                                                                                                        
                unsigned char c1,c2;                                                                                           
                c1 = ubuf[offset++]-'A';                                                                                       
                c2 = ubuf[offset++]-'A';                                                                                       
                if ((c1 & 0xF0) || (c2 & 0xF0)) {                                                                              
                        return(0);                                                                                             
                }                                                                                                              
                if (n >= sizeof(name->name)) {                                                                                 
                        return 0;                                                                                              
                }                                                                                                              
                name->name[n++] = (c1<<4) | c2;                                                                                
                m -= 2;                                                                                                        
        }      

well, there's a lot of offset++ without bounds checking.

If m is an odd number, then it will never get to <= 0 (because size_t), but if that can be the case I would expect more spectacular errors than this.
Comment 1 Douglas Bagnall 2020-01-17 00:01:15 UTC
BTW, I did notr find this in my private fuzzing because I didn't have --address-sanitizer in my configure line. I had removed that because it plays havoc with valgrind and gdb and did not seem to be finding new bugs.

The lesson is, have two fuzzing trees -- one for fuzzing with asan enabled, and one without for debugging.
Comment 2 Douglas Bagnall 2020-01-17 00:08:03 UTC
(In reply to Douglas Bagnall from comment #0)

> If m is an odd number, then it will never get to <= 0 (because size_t)

Indeed m starts off at 1.
Comment 3 Douglas Bagnall 2020-01-17 00:08:47 UTC
Created attachment 15736 [details]
[patch] this might be all we need
Comment 4 Jeremy Allison 2020-01-17 00:21:10 UTC
Comment on attachment 15736 [details]
[patch] this might be all we need

Oh, good catch ! Do you think this is a CVE-level problem ? m is read from the incoming packet, so it can be set to 1 by the attacker.

The following code:

 213                 if (n >= sizeof(name->name)) {
 214                         return 0;
 215                 }
 216                 name->name[n++] = (c1<<4) | c2;

will break us out of the loop even if m wraps through zero with the -2.
Comment 5 Jeremy Allison 2020-01-17 00:21:42 UTC
(In reply to Jeremy Allison from comment #4)

So I'm guessing we can only overwrite by 1 byte.
Comment 6 Douglas Bagnall 2020-01-17 00:30:52 UTC
(In reply to Jeremy Allison from comment #5)

I think we can't overwrite, but we can over-read by 1-15 bytes?

It will return 0 before writing into name->name[16]
Comment 7 Douglas Bagnall 2020-01-17 00:34:14 UTC
(In reply to Douglas Bagnall from comment #6)

Ah, but we can't overread, because we have checked offset + m against length.

So no security issue?
Comment 8 Jeremy Allison 2020-01-17 01:03:03 UTC
Created attachment 15737 [details]
raw test patch.

I don't think it's a security issue - just a one-byte overread.

What do you think of the following patch ?

Does it make things clearer ?

I'd love to get rid of the horrible 'int' variables in this code, but it's old and fragile enough I'm loathe to mess with it.
Comment 9 Douglas Bagnall 2020-01-17 01:10:51 UTC
What this

if ((m & 0xC0) ||

in the existing code is *really* saying is

if ((m >= 32) ||

It would be worth putting it that way while we're there:

if (m < 2 || m > 30) {
Comment 10 Jeremy Allison 2020-01-17 22:50:41 UTC
(In reply to Douglas Bagnall from comment #9)

> in the existing code is *really* saying is
>
> if ((m >= 32) ||

Actually it appears to be 'if (m > 32)', as the
value of m for a standard name lookup seems to
be always 32 (from additional debugs I added).
Comment 11 Jeremy Allison 2020-01-17 22:55:18 UTC
Yep, it's not null terminated - the 'name type' is in the last byte.

Here's how we post-process it once it's been read into name->name:

from source3/include/smb.h:

660 #define MAX_NETBIOSNAME_LEN 16
661 /* DOS character, NetBIOS namestring. Type used on the wire. */
662 typedef char nstring[MAX_NETBIOSNAME_LEN];
663 /* Unix character, NetBIOS namestring. Type used to manipulate name in nmbd.
664 typedef char unstring[MAX_NETBIOSNAME_LEN*4];
665 
666 /* A netbios name structure. */
667 struct nmb_name {
668         nstring      name;
669         char         scope[64];
670         unsigned int name_type;
671 };

from source3/libsmb/nmblib.c:

 230         /*
 231          * RFC1002: For a valid NetBIOS name, exiting from the above,
 232          * n *must* be MAX_NETBIOSNAME_LEN (16).
 233          */
 234         if (n != MAX_NETBIOSNAME_LEN) {
 235                 return 0;
 236         }
 237 
 238         /* parse out the name type, its always
 239          * in the 16th byte of the name */
 240         name->name_type = ((unsigned char)name->name[15]) & 0xff;
 241 
 242         /* remove trailing spaces */
 243         name->name[15] = 0;
 244         n = 14;
 245         while (n && name->name[n]==' ')
 246                 name->name[n--] = 0;

So the max length is 32, not 30.
Comment 12 Jeremy Allison 2020-01-17 22:55:38 UTC
Yep, it's not null terminated - the 'name type' is in the last byte.

Here's how we post-process it once it's been read into name->name:

from source3/include/smb.h:

660 #define MAX_NETBIOSNAME_LEN 16
661 /* DOS character, NetBIOS namestring. Type used on the wire. */
662 typedef char nstring[MAX_NETBIOSNAME_LEN];
663 /* Unix character, NetBIOS namestring. Type used to manipulate name in nmbd.
664 typedef char unstring[MAX_NETBIOSNAME_LEN*4];
665 
666 /* A netbios name structure. */
667 struct nmb_name {
668         nstring      name;
669         char         scope[64];
670         unsigned int name_type;
671 };

from source3/libsmb/nmblib.c:

 230         /*
 231          * RFC1002: For a valid NetBIOS name, exiting from the above,
 232          * n *must* be MAX_NETBIOSNAME_LEN (16).
 233          */
 234         if (n != MAX_NETBIOSNAME_LEN) {
 235                 return 0;
 236         }
 237 
 238         /* parse out the name type, its always
 239          * in the 16th byte of the name */
 240         name->name_type = ((unsigned char)name->name[15]) & 0xff;
 241 
 242         /* remove trailing spaces */
 243         name->name[15] = 0;
 244         n = 14;
 245         while (n && name->name[n]==' ')
 246                 name->name[n--] = 0;

So the max length is 32, not 30.
Comment 13 Douglas Bagnall 2020-01-18 04:12:35 UTC
(In reply to Jeremy Allison from comment #10)
Ah right.

As I noted on the merge request, because of the 

 234         if (n != MAX_NETBIOSNAME_LEN) {
 235                 return 0;
 236         }

I see no reason we should accept anything but 32. It's going to fail anyway.
Comment 14 Jeremy Allison 2020-01-18 04:15:22 UTC
Yep, I think your fix on the merge request is the right one.

I'll try and make the change over the weekend and re-push to the CI (unless you do it first :-).
Comment 15 Jeremy Allison 2020-01-23 00:51:33 UTC
Created attachment 15749 [details]
git-am fix that went into master

Applies cleanly to 4.12.rc1, 4.11.next, 4.10.next, 4.9.next.
Comment 16 Jeremy Allison 2020-01-23 18:27:19 UTC
Re-assigning to Karolin for inclusion in 4.12rcNext, 4.11.next, 4.10.next, 4.9.next.
Comment 17 Karolin Seeger 2020-01-31 10:47:30 UTC
Pushed to autobuild-v4-{12,11,10}-test.
Comment 18 Karolin Seeger 2020-02-04 09:58:31 UTC
(In reply to Karolin Seeger from comment #17)
Pushed to all branches.
Closing out bug report.

Thanks!
Comment 19 Douglas Bagnall 2026-07-29 02:05:10 UTC
Removing embargo, which was left on by mistake after it was closed.

We decided this was not a security issue.

It was fixed with ad236bb7590e423b4c69fe6028f2f3495977f48b