Closed p5pRT closed 7 years ago
Hello\,
I've attached the poc and the asan log. Tested on git version of perl.
Configure options:
“./Configure -des -Dusedevel -DDEBUGGING -Dcc=clang -Doptimize=-O2 -Accflags="-fsanitize=address -fsanitize-coverage=edge" -Aldflags="-fsanitize=address -fsanitize-coverage=edge" -Alddlflags=-shared"
Information about configuration:
Distributor ID: Ubuntu Description: Ubuntu 16.10 Release: 16.10 Codename: yakkety Arch: x86_64
Best Regards\, Marcin T.
==11621==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6020000011c8 at pc 0x000000ad3bec bp 0x7ffc300e3670 sp 0x7ffc300e3668 READ of size 1 at 0x6020000011c8 thread T0 #0 0xad3beb in Perl_do_vecget /home/mtowalski/Fuzzing/Programs/perl-git/doop.c:881:9 #1 0x9c20fe in Perl_pp_vec /home/mtowalski/Fuzzing/Programs/perl-git/pp.c:3495:5 #2 0x7fbc44 in Perl_runops_debug /home/mtowalski/Fuzzing/Programs/perl-git/dump.c:2451:23 #3 0x5e7bb3 in perl_run /home/mtowalski/Fuzzing/Programs/perl-git/perl.c #4 0x524302 in main /home/mtowalski/Fuzzing/Programs/perl-git/perlmain.c:123:9 #5 0x7f3f6106b3f0 in __libc_start_main /build/glibc-jxM2Ev/glibc-2.24/csu/../csu/libc-start.c:291 #6 0x4356f9 in _start (/home/mtowalski/Fuzzing/Programs/perl-git/perl+0x4356f9)
0x6020000011c8 is located 8 bytes to the left of 10-byte region [0x6020000011d0\,0x6020000011da) allocated by thread T0 here: #0 0x4eb0a8 in malloc (/home/mtowalski/Fuzzing/Programs/perl-git/perl+0x4eb0a8) #1 0x80087e in Perl_safesysmalloc /home/mtowalski/Fuzzing/Programs/perl-git/util.c:153:21
SUMMARY: AddressSanitizer: heap-buffer-overflow /home/mtowalski/Fuzzing/Programs/perl-git/doop.c:881:9 in Perl_do_vecget Shadow bytes around the buggy address: 0x0c047fff81e0: fa fa 00 02 fa fa 07 fa fa fa 02 fa fa fa fd fd 0x0c047fff81f0: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fd 0x0c047fff8200: fa fa 00 fa fa fa 00 00 fa fa fd fd fa fa fd fd 0x0c047fff8210: fa fa fd fd fa fa 00 fa fa fa 00 04 fa fa 00 02 0x0c047fff8220: fa fa 00 03 fa fa 00 fa fa fa 00 00 fa fa fd fd =>0x0c047fff8230: fa fa fd fa fa fa 00 02 fa[fa]00 02 fa fa fd fa 0x0c047fff8240: fa fa 00 02 fa fa fa fa fa fa fa fa fa fa fa fa 0x0c047fff8250: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c047fff8260: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c047fff8270: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c047fff8280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb ==11621==ABORTING
via RT \perl5\-security\-report@​perl\.org wrote:
I've attached the poc and the asan log.
This reduces to:
vec($x = ""\, 9223372036854775807\, 16)
(where the large constant is 2**63 - 1) and is caused by an integer overflow in Perl_do_vecget.
For this to be a security vulnerability\, the attacker must be able to control the offset of a vec() call with size > 8. (Or to control both offset and size\, but since the size is constrained to positive powers of two no greater than the number of bits in an IV\, that's less of an issue.) The consequence is an arbitrary memory read\, afaict.
However\, there's also a related bug in Perl_do_vecset; this further requires the vec() to be in lvalue context\, but I think it allows writing to arbitrary attacker-controlled memory locations. This seems like a greater hazard; does it need a CVE?
I've attached the following series of patches:
1. Fix the Perl_do_vecget overflow reported in this ticket
2. Fix the related Perl_do_vecset overflow\, by throwing an exception in that case
3. Fix another vec() bug\, found by inspection\, that happens for large offsets when IV is wider than size_t
4. Fix the equivalent case for lvalue vec(); not only does this throw an exception\, but the exception must be thrown when doing the initial read (so calling the vec() in lvalue context will throw an exception even if you don't subsequently write through the result)
5. Simplify do_vecget and do_vecset by removing a level of nesting in three places
6. Reindent do_vecget and do_vecset to account for the previous change
I suggest that 1\, 2\, 3\, and 4 (or other fixes for those bugs) should go into (frozen) blead in time for 5.26.0.
However\, patches 2 and 4 are slightly tricky from the point of view of the freeze\, in that they add a new exception. An alternative approach would be to silently do nothing in such situations\, but I don't think that's actually helpful for users.
I also think that 5 and 6 should go in too: the risk they present is negligible\, and they make it easier to see what the code is doing. But I accept that they aren't technically necessary during the freeze\, so I'm OK with holding them over to 5.27.
-- Aaron Crane ** http://aaroncrane.co.uk/
The RT System itself - Status changed from 'new' to 'open'
On Sun\, Mar 05\, 2017 at 03:04:06PM +0000\, Aaron Crane wrote:
via RT \perl5\-security\-report@​perl\.org wrote:
I've attached the poc and the asan log.
This reduces to:
vec($x = ""\, 9223372036854775807\, 16)
(where the large constant is 2**63 - 1) and is caused by an integer overflow in Perl_do_vecget.
For this to be a security vulnerability\, the attacker must be able to control the offset of a vec() call with size > 8. (Or to control both offset and size\, but since the size is constrained to positive powers of two no greater than the number of bits in an IV\, that's less of an issue.) The consequence is an arbitrary memory read\, afaict.
However\, there's also a related bug in Perl_do_vecset; this further requires the vec() to be in lvalue context\, but I think it allows writing to arbitrary attacker-controlled memory locations. This seems like a greater hazard; does it need a CVE?
From my own inspection of the existing code and your patches:
There are fundamentally two classes of bug here.
First are ones related to type mismatches such as signed \<=> unsigned\, Size_t vs SSize_t vs IV\, plus arithmetic overflow and wraparound. These can cause the wrong element to be fetched or written to (but never outside the string). For example (and this bug is still present after your patches)\, this:
my $s = "abcdefghijklmnopqrstuvwxyz"; my $n1 = vec $s\,0x0000000000000000 \, 64; my $n2 = vec $s\,0x4000000000000000 \, 64; printf "n1=0x%x n2=0x%x\n"\, $n1\, $n2;
prints
n1=0x6162636465666768 n2=0x6162636465666768
whereas n2 should be 0.
The second class of bug\, and the one reported in this ticket\, is where the calculation of len wraps in:
len = uoffset + (bitoffs + size + 7)/8;
here uoffset is the start byte within the string\, and len is supposed to be that plus the number of bytes needing to be read (2 in the case of bitsize 16). Thus for a value of uoffset very close to Size_t_MAX this calculation can wrap\, leaving len as a very small positive integer (e.g. 0 or 1 for bitsize 16\, 0..7 for bitsize 64). This defeats this guard:
if (len > srclen) ...
and causes the code to take the 'all the bytes are within the
string' branch which directly accesses the string using indexes like
s[uoffset] and s[uoffset + 1]\, without any further guards.
The net effect of all this is that\, AFAICT\, the wild read can only be of the word directly preceding the start of the string\, not of any arbitrary attacker-controlled location.
Similarly in Perl_do_vecset()\, the code only fails to attempt to grow the buffer to the size of offset if the len calculation wrapped; so again the code can only write to the word directly before the string buffer rather than to any location in memory. Not ideal\, but given this can only occur when the attacker can cause the code to call vec() with a specified very large offset and a bit size >= 8\, I think we can skip the CVE.
I'd be happier if someone checked my logic above though.
I'm not entirely happy with your patchset\, chiefly in that I don't think it goes far enough. Some issues I see:
* In pp_vec():
const IV offset = POPi;
If the offset happens to be a large-valued UV\, then offset will be set to a negative value. This is safe\, but will give a misleading error in lval context:
$ perl -e'vec($x=""\, ~0\, 8) = 1' Negative offset to vec in lvalue context at -e line 1.
(there is no negative value).
* I think the signature of Perl_do_vecget() should change: offset should change from SSize_t to STRLEN (aka Size_t). This is because both string lengths and LvTARGOFF() are STRLEN and we're doing a bunch of undefined (but in practice safe) behaviour by storing a signed value as unsigned then retrieving it and treating it signed again. Changing this reduces the cognitive load. pp_vec()'s job then becomes to take an arbitrarily-valued SV and convert it into a STRLEN value ready to pass to do_vecget() (or via LvTARGOFF to do_vecget or do_vecset). Your patches already mostly do this\, but I would prefer to see it more explicit - i.e. a block of code which starts with an SV and ands with a STRLEN-valued variable ready to be used by the rest of the function (or which dies/returns 0 trying).
Perl_do_vecget() is 'p' in embed.fnc\, so shouldn't be used outside the core; in fact on win32 it *cannot* be used outside the core because it isn't exported. Also\, grep.cpan.me shows no use of it.
* In do_vecget and do_vecset\, the calculations where we do things like
uoffset = 2*offset; len = uoffset + 2;
both risk overflowing. I think it is more defensive to check for sane values before the calculations rather than check for insane values after; e.g.
if (offset > SSize_t_MAX / 2) croak(...); uoffset = 2*offset;
Do you want to rework your patches\, or would you like me to have a go? (I don't mind - I am available to work on it immediately).
I still think this should be fixed for 5.25.11.
However\, patches 2 and 4 are slightly tricky from the point of view of the freeze\, in that they add a new exception. An alternative approach would be to silently do nothing in such situations\, but I don't think that's actually helpful for users.
We already have some generic out-of-memory error messages that although are less specific\, can suffice without needing a new diag. For example already:
$ perl -e'my $s=""; vec($s\,1\<\<40\,8) = 1' Out of memory!
We don't necessarily need the special error of
Offset too large for vec in lvalue context
just because we're running on a 32-bit system with -Duse64bitint and provided an offset > 2^31. I think 'Out of memory!' works just as well there too.
I also think that 5 and 6 should go in too: the risk they present is negligible\, and they make it easier to see what the code is doing. But I accept that they aren't technically necessary during the freeze\, so I'm OK with holding them over to 5.27.
I'm happy for something similar to go in.
-- I before E. Except when it isn't.
On Tue\, Mar 14\, 2017 at 05:44:38PM +0000\, Aaron Crane wrote:
Thank you for the offer\, and please go ahead — I moved house yesterday\, so it's going to be a little while before I've got enough time to think carefully about things like this.
Ok here are two commits that I will merge into blead tomorrow unless I hear anything to the contrary. I decided not to remove the redundant sets of braces in the big if/else tree for now.
I've tested them on a 64-bit system and a 32-bit system with -Duse64bitint\, and am currently testing with a pure 32-bit build.
-- Music lesson: a symbiotic relationship whereby a pupil's embellishments concerning the amount of practice performed since the last lesson are rewarded with embellishments from the teacher concerning the pupil's progress over the corresponding period.
On Thu\, Mar 16\, 2017 at 03:15:42PM +0000\, Dave Mitchell wrote:
On Tue\, Mar 14\, 2017 at 05:44:38PM +0000\, Aaron Crane wrote:
Thank you for the offer\, and please go ahead — I moved house yesterday\, so it's going to be a little while before I've got enough time to think carefully about things like this.
Ok here are two commits that I will merge into blead tomorrow unless I hear anything to the contrary. I decided not to remove the redundant sets of braces in the big if/else tree for now.
I've tested them on a 64-bit system and a 32-bit system with -Duse64bitint\, and am currently testing with a pure 32-bit build.
Now pushed\, as
commit 281fe5e7055b0d2374f99ba00af0e45f22386854 Merge: 7e337d2 67dd6f3 Author: David Mitchell \davem@​iabyn\.com AuthorDate: Fri Mar 17 14:13:57 2017 +0000 Commit: David Mitchell \davem@​iabyn\.com CommitDate: Fri Mar 17 14:13:57 2017 +0000
[MERGE] fix vec() offset overflow issues
unless anyone objects\, I'll move this ticket to the public queue and close it.
-- Wesley Crusher gets beaten up by his classmates for being a smarmy git\, and consequently has a go at making some friends of his own age for a change. -- Things That Never Happen in "Star Trek" #18
@iabyn - Status changed from 'open' to 'pending release'
Thank you for filing this report. You have helped make Perl better.
With the release today of Perl 5.26.0\, this and 210 other issues have been resolved.
Perl 5.26.0 may be downloaded via: https://metacpan.org/release/XSAWYERX/perl-5.26.0
If you find that the problem persists\, feel free to reopen this ticket.
@khwilliamson - Status changed from 'pending release' to 'resolved'
Migrated from rt.perl.org#130915 (status was 'resolved')
Searchable as RT130915$