Bug 862413 - (CVE-2012-5195) CVE-2012-5195 perl: heap buffer overrun flaw may lead to arbitrary code execution
CVE-2012-5195 perl: heap buffer overrun flaw may lead to arbitrary code execu...
Status: CLOSED ERRATA
Product: Security Response
Classification: Other
Component: vulnerability (Show other bugs)
unspecified
All Linux
medium Severity medium
: ---
: ---
Assigned To: Red Hat Product Security
impact=moderate,public=20121010,repor...
: Security
Depends On: 915690 915691 915692 915693
Blocks: 862414
  Show dependency treegraph
 
Reported: 2012-10-02 16:02 EDT by Vincent Danen
Modified: 2013-03-26 15:56 EDT (History)
8 users (show)

See Also:
Fixed In Version:
Doc Type: Bug Fix
Doc Text:
Story Points: ---
Clone Of:
Environment:
Last Closed: 2013-03-26 15:56:14 EDT
Type: ---
Regression: ---
Mount Type: ---
Documentation: ---
CRM:
Verified Versions:
Category: ---
oVirt Team: ---
RHEL 7.3 requirements from Atomic Host:


Attachments (Terms of Use)
memset wrapper (1.61 KB, text/plain)
2012-10-09 05:27 EDT, Petr Pisar
no flags Details
Proposed fix for perl-5.10.1 (1.07 KB, patch)
2012-10-09 12:25 EDT, Petr Pisar
no flags Details | Diff
Upstream fix for perl-5.12 and 5.14 (1.05 KB, patch)
2012-10-10 08:33 EDT, Petr Pisar
no flags Details | Diff
Amendment for 5.10 (905 bytes, patch)
2013-03-04 04:20 EST, Petr Pisar
no flags Details | Diff

  None (edit)
Description Vincent Danen 2012-10-02 16:02:28 EDT
The following flaw was reported in perl versions prior to 5.15.5 in conjunction with glibc versions prior to 2.16:

The Perl programming language has a string repeat operator, 'x'. For
example, the expression ("ab-" x 4) evaluates to "ab-ab-ab-ab-". In the
case where the string on the LHS is a single character long, and where the
count on the right is greater than 2Gb, a truncation / sign-extension
issue on older perls will cause the memory needed for the new string to be
allocated correctly, but the code that fills that buffer with copies of
the original character may loop past the end of the buffer, probably
continuing until an unmapped memory region is reached and a SEGV occurs.
It may be possible to adjust the count so that the buffer is overrun, but
the SEGV isn't reached.

The buffer is filled using the C library's memset() function; in versions
of glibc before 2.16, it didn't check for a negative count, and this could
cause it to access a negative offset in a jump table, and possibly jump
into arbitrary code.

Note that perl code is only vulnerable where the attacker can control the
count argument of the repeat operator; such poorly-written code is already
susceptible to a denial-of-service attack; the flaws in older perls and
glibc escalate this into buffer overruns and possible arbitrary code
execution.

This patch addresses the issue:

> diff --git a/util.c b/util.c
> index 0ea39c6..110da99 100644
> --- a/util.c
> +++ b/util.c
> @@ -3319,6 +3319,8 @@ Perl_repeatcpy(register char *to, register const char   *from, I32 len, register I
>  {
>      PERL_ARGS_ASSERT_REPEATCPY;
>
> +    if (count < 0)
> +     Perl_croak_nocontext("%s",PL_memory_wrap);
>      if (len == 1)
>       memset(to, *from, count);
>      else if (count) {
>
Comment 2 Petr Pisar 2012-10-03 08:38:24 EDT
Can you test it?

I give a try on x86_64 Fedora 17 with perl 5.14.2 and glibc-2.15-57.fc17. Unfortunately I have 4 GB of physical memory only.

$ perl -e '$a = q{a} x 2**31+0;'

consumes more than 2 GB however it swaps a lot.

$ perl -e '$a = q{a} x 2**31+1;'

terminates quickly.

Debugger shows both `count' variable in perl:Perl_repeatcpy() and `__len' variable in glibc:memset() are 8 bytes long, so no trim should happen.

However I do not understand why 2**31+1 is so fast. Like the counter was wrapped somewhere underneath in the glibc. Unfortunately memset() cannot report any error. It should return its first argument always.
Comment 3 Petr Pisar 2012-10-03 09:18:39 EDT
I see why I cannot reproduce it with x86_64 Fedora. Per bug #720610, we have applied patch making the Perl_repeatcpy() safe for repeat counter > 2^31 by changing the function prototype. Thus the count argument is never wrapped.

The patch lives in Fedora since release 14 (perl 5.12.4). The patch is part of upstream since perl 5.16.0.

However the reason why 2**32+1 finished quickly is not clear for me yet.
Comment 4 Petr Pisar 2012-10-03 09:23:35 EDT
(In reply to comment #0)
> 
> The buffer is filled using the C library's memset() function; in versions
> of glibc before 2.16, it didn't check for a negative count

How can glibc check for negative count, if the count argument is of type size_t which is unsigned by definition?
Comment 5 Vincent Danen 2012-10-03 13:31:03 EDT
I've asked upstream for further details yesterday, but they have not yet responded.  Have you tried with a 32bit system?  I'll give this a go this afternoon on some 32bit virtual machines (with smaller amounts of RAM) to see if it can be reproduced.

I am also unsure of the correlation with glibc; we may need someone more familiar with glibc to check why this is the case.  Perhaps Jeff can enlighten us?
Comment 6 Jeff Law 2012-10-03 13:48:00 EDT
In response to c#4, it could check for the high bit set on the count.  That would indicate a "negative" value if the value was interpreted as a signed value.  However, I think that returning an error or not performing the memset would be a standards violation.

Note that glibc's memset did have a bug in which it was doing signed comparisons of the size argument when it should have been doing unsigned comparisons.  This could result in an incorrect index being computed for the jumptable and jumping to a unexpected location.  This bug was fixed by the following commit:

commit e80d6f94e19d17b91e3cd3ada7193cc88f621feb
Author: Michael Matz <matz@suse.de>
Date:   Thu Apr 5 10:48:14 2012 +0200

    Fix size parameter comparisions.
    
    [BZ #13592]
    There are several signed compares of the size argument, whereas
    it really is unsigned.  Depending on situations e.g. a "memset(ptr, 0,
    -1)" segfault (but for the wrong reasons, because jumping into nirvana)
    or succeeds even.
    
    In normal use this is harmless, as a size with signbit set indicates
    more than half the address space which on x86_64 is impossible to
    allocate, but as the size is used to index some jump tables this
    potentially could have other unwanted side effects.
Comment 7 Petr Pisar 2012-10-04 07:27:09 EDT
(In reply to comment #5)
> Have you tried with a 32bit system?  
No.
Comment 9 Vincent Danen 2012-10-04 12:05:47 EDT
(In reply to comment #6)

> Note that glibc's memset did have a bug in which it was doing signed
> comparisons of the size argument when it should have been doing unsigned
> comparisons.  This could result in an incorrect index being computed for the
> jumptable and jumping to a unexpected location.  This bug was fixed by the
> following commit:

Jeff, did glibc always have this bug?  Or do we know when, or in what version, it may have been introduced?
Comment 11 Jeff Law 2012-10-04 13:24:38 EDT
Upstream it appears to have been introduced in glibc-2.7, circa late 2007 or early 2008.  It looks like the buggy code was backported into RHEL 5.

The fixes went into F18.

So RHEL 5 & 6 and Fedora 16 & 17 have this glibc bug.

jeff
Comment 13 Petr Pisar 2012-10-05 03:58:10 EDT
I'd say there are two problems:

(1) glibc mishandles memset() count argument. This should be fixed in glibc.

(2) perl can pass count value to memset() not fitting into size_t type. This should be fixed in perl.

Now the perl issue. We have again two problems:

(2a) Perl_repeatcpy() count argument is of type I32 but the function is called from various places with variables of bigger type, IV, and thus the value can be clamped.

This is issue on RHEL only as Fedora has changed the prototype already (therefore I think upstream will not comment Fedora builds). Also this is problem on x86_64 where IV is bigger than I32 for sure. Whereas this might not be issue on x86 (but I'm not sure about the actual size of IV on the platform).

Unfortunately we cannot apply the Fedora patch to RHEL because it changes prototype of public Perl_repeatcpy() which would break ABI. However I think the breakage would not be so serious because we know about only one another piece of software on CPAN calling this function, so I doubt the function is used by anybody else.

This is not problem on x86 because you cannot allocate more than 2**32 there.
This is problem on x86_64.

We could find all Perl_repeatcpy() calls from perl and guard them with a condition.

(2b) Perl_repeatcpy() passes I32/IV on RHEL/Fedora to memset(). This can be problem on platforms where sizeof(IV) > sizof(size_t) for value overrun and this is definitely problem when negative value is passed because I32/IV are signed.

This can be fixed easily by adding check for negative value into  Perl_repeatcpy() as shown in comment #1.
Comment 16 Petr Pisar 2012-10-09 05:27:10 EDT
Created attachment 624012 [details]
memset wrapper

This is wrapper for libc memset(). It prints arguments and checks whether the memory has been set by libc memset() properly. It aborts in case of an error.

Compile as

$(CC) -Wall --std=c99 $< --shared -DPIC -fPIC -o $@ -ldl

and the LD_PRELOAD the shared library.
Comment 17 Petr Pisar 2012-10-09 08:16:12 EDT
* RHEL-5 perl-5.8.8:

Perl_repeatcpy() does not call memset. The count argument is of type I32. It's called from 3 places:

pp.c:1472:
  count passed as IV. Guarded by "if (count > 1)" on line 1440 already.
pp.c:1498:
  count passed as IV. Guarded by "if (count < 1) {} else" on line 1490 already.
regcomp.c:1302:
  count passed as I32. Guarded by "if (mincount > 1)" on line 1300 already.

Resolution: We need to check for count > 2<<(sizeof(I32)-1) at the three places and croak before.


* RHEL-6 perl-5.10.1:

Perl_repeatcpy() does not call memset. The count argument is of type I32. It's called from 3 places:

pp.c:1556:
  The same as perl-5.8.8.
pp.c:1582:
  The same as perl-5.8.8.
regcomp.c:3357:
  The same as perl-5.8.8.

Resolution: We need to check for count > 2<<(sizeof(I32)-1) at the three places and croak before.


* Fedora <= 17 perl-5.14.2:

Perl_repeatcpy() calls memset. The count argument is of type IV. It's called from 3 places:

pp.c:1786:
  Already guarded by "if (count > 1)" on line 1754.
pp.c:1813:
  Already guarded by "if (count < 1) {} else" on line 1805.
regcomp.c:3640:
  Already guarded by "if (mincount > 1)" on line 3638.

Resolution: We need to check for count > SIZE_MAX in Perl_repeatcpy() and croak before. We can add check for negative value at the same place because Perl_repeatcpy() is public function.


* Fedora >= 18 perl-5.16.1:

Perl_repeatcpy() calls memset. The count argument is of type IV. It's called from 1 place:

regcomp.c:3999:
  Already guarded by "if (mincount > 1): on line 3997.

Resolution: We need to check for count > SIZE_MAX in Perl_repeatcpy() and croak before. We can add check for negative value at the same place because Perl_repeatcpy() is public function.
Comment 18 Petr Pisar 2012-10-09 10:38:01 EDT
Correction to RHEL: We need to add the checks only at places where IV is passed as I32. That means only to the first two places.
Comment 19 Petr Pisar 2012-10-09 10:45:01 EDT
(In reply to comment #2)
> Can you test it?
> 
> I give a try on x86_64 Fedora 17 with perl 5.14.2 and glibc-2.15-57.fc17.
> Unfortunately I have 4 GB of physical memory only.
> 
> $ perl -e '$a = q{a} x 2**31+0;'
> 
> consumes more than 2 GB however it swaps a lot.
> 
> $ perl -e '$a = q{a} x 2**31+1;'
> 
Important notice to the reproducer: Correct notation is `q{a} x (2**31+1)'. Without the parenthesis, the part after the exponent is ignored.

With this I can see in debugger on RHEL-6 the IV-to-I32 overflow:

(gdb) set args -e '$a = q{a} x (2**31+1);'
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /home/test/rhel/perl/perl-5.10.1/perl -e '$a = q{a} x (2**31+1);'
[Thread debugging using libthread_db enabled]

Breakpoint 1, Perl_repeatcpy (my_perl=0x602010, to=0x7fff7093b011 "", 
    from=0x7fff7093b010 "a", len=1, count=-2147483648) at util.c:3078
3078        if (len == 1) {
Comment 21 Petr Pisar 2012-10-09 12:25:27 EDT
Created attachment 624190 [details]
Proposed fix for perl-5.10.1

This checks for IV-to-I32 wrap when calling Perl_repeatcpy(). It does not need to check for negative values because they are checked on other places. It's not necessary to do checks inside the function because it does not pass the value to memset() and it operates on negative values correctly.
Comment 23 Petr Pisar 2012-10-10 05:03:15 EDT
The 5.10.1 patch applies and fixes this bug in RHEL-5 perl-5.8.8 too. Verified with gdb as described in comment #19.
Comment 24 Petr Pisar 2012-10-10 08:17:14 EDT
If you install perl(Inline::C), you can see on Fedora 17, that negative count argument dooms the memory:

$ LD_PRELOAD=~/perl/repeatcpy-CVE-2012-5195/memset.so perl -e 'use Inline C => q/void test(){ char *p = malloc(1); Perl_repeatcpy(p, "a", 1, -1);}/; test();'
[...]
Called: memset(source=0x2172120, value=0, count=48)
Called: memset(source=0x22843b0, value=97, count=18446744073709551615)
memset() did not set byte at offset 0 (0x22843b0[0]=144 != 97)!
Neúspěšně ukončen (SIGABRT)

If you remove the memset checker and replace the count argument -1 to -42, you can get segfault:

$ perl -e 'use Inline C => q/void test(){ char *p = malloc(1); Perl_repeatcpy(p, "a", 1, -42);}/; test();'
Neoprávněný přístup do paměti (SIGSEGV)

I can rewrite the test to XS (because Inline::C is clumsy as it calls GCC), but I don't think it's valuable.

Actually calling Perl_repeatcpy with argument mismatching length of allocated buffer passed as first argument violates API. And because SIZE_MAX > IV_MAX, I think there is nothing to fix in Fedora. (Well, some platforms could have the condition false, like PPC32, but I have not machine to check it.)

So I tend to mark Fedora as unaffected.
Comment 25 Petr Pisar 2012-10-10 08:33:48 EDT
Created attachment 624842 [details]
Upstream fix for perl-5.12 and 5.14

Upstream has just pushed this commit identical to code from comment #1 into 5.12 and 5.14 branches only.
Comment 33 Petr Pisar 2013-03-04 04:20:50 EST
Created attachment 704824 [details]
Amendment for 5.10

This patch is needed to prevent from silent data corruption on i686.
Comment 34 Petr Pisar 2013-03-04 04:35:46 EST
RHEL-6 already fixed in perl-5.10.1-129.el6 as bug #720644.
Comment 35 Petr Pisar 2013-03-04 10:17:05 EST
5.10 patches are applicable to 5.8.8.
Comment 36 Vincent Danen 2013-03-26 13:15:50 EDT
Acknowledgements:

Red Hat would like to thank the Perl project for reporting this issue.  Upstream acknowledges Tim Brown as the original reporter.
Comment 37 errata-xmlrpc 2013-03-26 15:27:29 EDT
This issue has been addressed in following products:

  Red Hat Enterprise Linux 5
  Red Hat Enterprise Linux 6

Via RHSA-2013:0685 https://rhn.redhat.com/errata/RHSA-2013-0685.html

Note You need to log in before you can comment on or make changes to this bug.