Bug 14049 - ldb dn crash
Summary: ldb dn crash
Status: RESOLVED FIXED
Alias: None
Product: Samba 4.1 and newer
Classification: Unclassified
Component: AD: LDB/DSDB/SAMDB (show other bugs)
Version: unspecified
Hardware: All All
: P5 normal (vote)
Target Milestone: ---
Assignee: Andrew Bartlett
QA Contact: Samba QA Contact
URL:
Keywords:
Depends on: 14104
Blocks:
  Show dependency treegraph
 
Reported: 2019-07-24 06:08 UTC by Douglas Bagnall
Modified: 2020-01-10 00:20 UTC (History)
1 user (show)

See Also:


Attachments
proof of concept crasher (2.26 KB, patch)
2019-07-25 01:18 UTC, Douglas Bagnall
no flags Details
a patch that stops the crash (2.24 KB, patch)
2019-07-25 21:52 UTC, Douglas Bagnall
no flags Details
a patch that stops the crash (v2) (1.93 KB, patch)
2019-07-25 22:06 UTC, Douglas Bagnall
no flags Details
a script to look for ldb_dn segfaults (3.36 KB, patch)
2019-07-26 04:12 UTC, Douglas Bagnall
no flags Details
updated script to look for ldb_dn segfaults (4.90 KB, patch)
2019-08-07 01:02 UTC, Douglas Bagnall
no flags Details
some ldb_dn explode tests (3.09 KB, patch)
2019-08-20 21:46 UTC, Douglas Bagnall
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Douglas Bagnall 2019-07-24 06:08:36 UTC
$ python3 -c "from samba import ldb;a = ldb.Ldb(); ldb.Dn(a, '<>').canonical_str()"
Segmentation fault (core dumped)

This gets beyond the Python bindings. I think the problem is there are no components, but ldb missed that because it expected there were extended components, or something.


#0  __strcasecmp_l_avx () at ../sysdeps/x86_64/multiarch/strcmp-sse42.S:270
#1  0x00007ffff73c8b53 in ldb_dn_canonical (mem_ctx=0x9a7f50, dn=0x9a7f50, ex_format=0) at ../../lib/ldb/common/ldb_dn.c:1818
#2  0x00007ffff73c8d75 in ldb_dn_canonical_string (mem_ctx=0x9a7f50, dn=0x9a7f50) at ../../lib/ldb/common/ldb_dn.c:1865
#3  0x00007ffff73f1e4e in py_ldb_dn_canonical_str (self=0x7ffff7597650, _unused_ignored=0x0) at ../../lib/ldb/pyldb.c:510
#4  0x00000000004d7f6a in _PyMethodDef_RawFastCallKeywords (kwnames=<optimized out>, nargs=<optimized out>, args=<optimized out>, 
    self=<ldb.Dn at remote 0x7ffff7597650>, method=0x7ffff74041a0 <py_ldb_dn_methods+192>) at ../Objects/call.c:629
#5  _PyMethodDescr_FastCallKeywords (descrobj=<method_descriptor at remote 0x7ffff74355a0>, args=0x7ffff75a2b68, nargs=<optimized out>, kwnames=<optimized out>)
    at ../Objects/descrobject.c:288
#6  0x0000000000552313 in call_function (kwnames=0x0, oparg=1, pp_stack=0x7fffffffb0c0) at ../Python/ceval.c:4593
#7  _PyEval_EvalFrameDefault (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3110
#8  0x000000000054b9f2 in PyEval_EvalFrameEx (throwflag=0, f=Frame 0x7ffff75a29f8, for file <string>, line 1, in <module> ()) at ../Python/ceval.c:547
#9  _PyEval_EvalCodeWithName (_co=<optimized out>, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kwnames=0x0, 
    kwargs=0x0, kwcount=<optimized out>, kwstep=2, defs=0x0, defcount=0, kwdefs=0x0, closure=0x0, name=0x0, qualname=0x0) at ../Python/ceval.c:3930
#10 0x000000000053109f in PyEval_EvalCodeEx (closure=0x0, kwdefs=0x0, defcount=0, defs=0x0, kwcount=0, kws=0x0, argcount=0, args=0x0, 
    locals={'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <type at remote 0x8deba8>, '__spec__': None, '__annotations__': {}, '__builtins__': <module at remote 0x7ffff75f9c28>, 'ldb': <module at remote 0x7ffff743a098>, 'a': <ldb.Ldb at remote 0x7ffff7584950>}, 
    globals={'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <type at remote 0x8deba8>, '__spec__': None, '__annotations__': {}, '__builtins__': <module at remote 0x7ffff75f9c28>, 'ldb': <module at remote 0x7ffff743a098>, 'a': <ldb.Ldb at remote 0x7ffff7584950>}, _co=<code at remote 0x7ffff74c55d0>)
    at ../Python/ceval.c:3959
#11 PyEval_EvalCode (
    locals={'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <type at remote 0x8deba8>, '__spec__': None, '__annotations__': {}, '__builtins__': <module at remote 0x7ffff75f9c28>, 'ldb': <module at remote 0x7ffff743a098>, 'a': <ldb.Ldb at remote 0x7ffff7584950>}, 
    globals={'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <type at remote 0x8deba8>, '__spec__': None, '__annotations__': {}, '__builtins__': <module at remote 0x7ffff75f9c28>, 'ldb': <module at remote 0x7ffff743a098>, 'a': <ldb.Ldb at remote 0x7ffff7584950>}, co=<code at remote 0x7ffff74c55d0>)
    at ../Python/ceval.c:524
#12 run_mod (arena=0x7ffff76002a0, flags=0x7fffffffb2ec, 
    locals={'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <type at remote 0x8deba8>, '__spec__': None, '__annotations__': {}, '__builtins__': <module at remote 0x7ffff75f9c28>, 'ldb': <module at remote 0x7ffff743a098>, 'a': <ldb.Ldb at remote 0x7ffff7584950>}, 
    globals={'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <type at remote 0x8deba8>, '__spec__': None, '__annotations__': {}, '__builtins__': <module at remote 0x7ffff75f9c28>, 'ldb': <module at remote 0x7ffff743a098>, 'a': <ldb.Ldb at remote 0x7ffff7584950>}, filename='<string>', mod=<optimized out>)
    at ../Python/pythonrun.c:1035
#13 PyRun_StringFlags (str=<optimized out>, start=257, 
    globals={'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <type at remote 0x8deba8>, '__spec__': None, '__annotations__': {}, '__builtins__': <module at remote 0x7ffff75f9c28>, 'ldb': <module at remote 0x7ffff743a098>, 'a': <ldb.Ldb at remote 0x7ffff7584950>}, 
    locals={'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <type at remote 0x8deba8>, '__spec__': None, '__annotations__': {}, '__builtins__': <module at remote 0x7ffff75f9c28>, 'ldb': <module at remote 0x7ffff743a098>, 'a': <ldb.Ldb at remote 0x7ffff7584950>}, flags=0x7fffffffb2ec)
    at ../Python/pythonrun.c:959
#14 0x00000000006319bd in PyRun_SimpleStringFlags (command=0x7ffff7517ae0 "from samba import ldb;a = ldb.Ldb(); ldb.Dn(a, '<>').canonical_str()\n", 
    flags=0x7fffffffb2ec) at ../Python/pythonrun.c:455
#15 0x0000000000654378 in pymain_run_command (cf=0x7fffffffb2ec, command=<optimized out>) at ../Modules/main.c:385
#16 pymain_run_python (pymain=0x7fffffffb3c0) at ../Modules/main.c:2871
#17 pymain_main (pymain=<optimized out>, pymain=<optimized out>) at ../Modules/main.c:3038
#18 0x00000000006544ae in _Py_UnixMain (argc=<optimized out>, argv=<optimized out>) at ../Modules/main.c:3073
#19 0x00007ffff7d8fb6b in __libc_start_main (main=0x4bc950 <main>, argc=3, argv=0x7fffffffb508, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, 
    stack_end=0x7fffffffb4f8) at ../csu/libc-start.c:308
#20 0x00000000005e04ea in _start () at ../Modules/main.c:725
Comment 1 Douglas Bagnall 2019-07-25 01:18:16 UTC
Created attachment 15323 [details]
proof of concept crasher

The attached file reduces it down to the essential calls (no Python).

after configure and make, 

$ ./bin/ldb_dn_crash
Segmentation fault (core dumped)

Results are the same on master, 4.10, 4.8, and 4.5.

ldb_dn_canonical_string() is not the only function that will trigger this -- ldb_dn_explode() is setting comp_num to 1.
Comment 2 Douglas Bagnall 2019-07-25 01:22:52 UTC
It seems likely that an unauthenticated user entity *somehow* cause the server to examine and manipulate arbitrary DN strings, though we haven't yet been able to trigger this over LDAP.
Comment 3 Douglas Bagnall 2019-07-25 21:52:40 UTC
Created attachment 15327 [details]
a patch that stops the crash

This patch is probably sufficient.

I have old patches sitting around that aim to decomplify ldb_dn_explode(), which I think might also be useful in master.
Comment 4 Douglas Bagnall 2019-07-25 22:06:15 UTC
Created attachment 15328 [details]
a patch that stops the crash (v2)
Comment 5 Douglas Bagnall 2019-07-25 22:34:45 UTC
(In reply to Douglas Bagnall from comment #3)

> I have old patches sitting around that aim to decomplify ldb_dn_explode(), which I think might also be useful in master.

That's this, which fixes the problem:
http://git.catalyst.net.nz/gitweb?p=samba.git;a=commitdiff;h=228ea5cf47d0e3a814cf430a7c31f0b4a6ca3b08
Comment 6 Douglas Bagnall 2019-07-26 04:12:27 UTC
Created attachment 15330 [details]
a script to look for ldb_dn segfaults

If the attached program does not crash, we are probably safe against any short string!
Comment 7 Douglas Bagnall 2019-08-07 01:02:30 UTC
Created attachment 15378 [details]
updated script to look for ldb_dn segfaults
Comment 8 Andrew Bartlett 2019-08-14 04:01:58 UTC
Is the crash only viable via ldb_dn_canonical_string()?

This (in Samba) is only accessible via trusted inputs, so we might have dodged this one.
Comment 9 Douglas Bagnall 2019-08-14 04:06:12 UTC
(In reply to Andrew Bartlett from comment #8)

> Is the crash only viable via ldb_dn_canonical_string()?

Alas, no.

Any function that iterates over the comonents crashes. 

There are a few of them.
Comment 10 Andrew Bartlett 2019-08-14 04:20:36 UTC
(In reply to Douglas Bagnall from comment #9)
So, how should we proceed from here?

I can't get ldb_dn_get_casefold() to crash on your simple reproducer.
Comment 11 Douglas Bagnall 2019-08-14 04:35:26 UTC
These are the routes I initially found:

py_ldb_dn_canonical_ex_str
py_ldb_dn_canonical_str
py_ldb_dn_extended_str
py_ldb_dn_get_casefold
py_ldb_dn_methods
py_ldb_dn_repr
Comment 12 Andrew Bartlett 2019-08-14 04:45:47 UTC
(In reply to Douglas Bagnall from comment #11)
I can't make ldb_dn_get_extended_linearized() crash. 

I'm thinking we escape this by the skin of our teeth, but proving a negative is hard.
Comment 13 Andrew Bartlett 2019-08-14 05:23:40 UTC
So, here is the plan.

Douglas and I have looked at this again and while this is a serious issue, it should be dealt with as a public bug as we can't find a path to an actual exploit over LDAP.

The most likely issue, if there is one, is a crash in the LDAP server (a DoS) which is now mitigated by a auto-restart for the prefork workers in 4.10 and later.  

Samba 4.11 will need to ship with this fixed

Samba 4.7 -> 4.10 use the forking LDAP server, making this a self-DoS for the default configuration (but an issue if -M single or -M prefork were specified).

Previous investigations did not find other projects that allow untrusted input into LDB DN functions.  

To allow any last-moment objections I'll wait 24 hours to make this public.  Then I'll propose one or other of the referenced patches to master and backport etc.
Comment 14 Andrew Bartlett 2019-08-16 03:36:32 UTC
Removing embargo, we will just fix this as an important but otherwise quite ordinary bug.
Comment 15 Andrew Bartlett 2019-08-16 10:02:03 UTC
(In reply to Douglas Bagnall from comment #5)
this patch no longer applies bug the fix from attachment 15328 [details] fixes it.

We will need a proper test, not just a PoC binary to merge this.
Comment 16 Douglas Bagnall 2019-08-20 21:46:00 UTC
Created attachment 15403 [details]
some ldb_dn explode tests

OK. I do have these tests I was working on a while ago.
Comment 17 Andrew Bartlett 2019-08-26 23:45:45 UTC
A MR with tests (which sadly fail) is here:
https://gitlab.com/samba-team/samba/merge_requests/737

I'll have a go at making those pass.