diff options
author | Pedro Alves <palves@redhat.com> | 2016-03-09 03:01:06 +0000 |
---|---|---|
committer | Pedro Alves <palves@redhat.com> | 2016-03-09 03:01:06 +0000 |
commit | d7a87b5e4343467db23b70e73b156c9cc79c50ea (patch) | |
tree | 15dcc4c7c3f5c6f2a3402846cb3cd0f337b3865b | |
parent | b79497cb1cdc9b3053e5f0387bf3056c08c9bbdd (diff) | |
download | gdb-d7a87b5e4343467db23b70e73b156c9cc79c50ea.zip gdb-d7a87b5e4343467db23b70e73b156c9cc79c50ea.tar.gz gdb-d7a87b5e4343467db23b70e73b156c9cc79c50ea.tar.bz2 |
Fix floating conversion buffer overrun when host/target format matches
Running the testsuite with a gdb configured with --enable-libmcheck
reveals a problem:
(gdb) ptype 3 * 2.0
type = <12-byte float>
memory clobbered past end of allocated block
ERROR: Process no longer exists
UNRESOLVED: gdb.ada/ptype_arith_binop.exp: ptype 3 * 2.0
(gdb) PASS: gdb.dlang/expression.exp: ptype 0x1.FFFFFFFFFFFFFp1023
ptype 0x1p-52L
type = real
memory clobbered past end of allocated block
ERROR: Process no longer exists
UNRESOLVED: gdb.dlang/expression.exp: ptype 0x1p-52L
Even though this shows up with Ada and D, it's easy to reproduce in C
too. We just need to print a long double, when the current arch is
32-bit, which is the default when gdb starts up:
$ ./gdb -q -ex "ptype 1.0L"
type = long double
memory clobbered past end of allocated block
Aborted (core dumped)
Valgrind shows:
==22159== Invalid write of size 8
==22159== at 0x8464A9: floatformat_from_doublest (doublest.c:756)
==22159== by 0x846822: store_typed_floating (doublest.c:867)
==22159== by 0x6A7959: value_from_double (value.c:3662)
==22159== by 0x6A9F2D: evaluate_subexp_standard (eval.c:745)
==22159== by 0x7F31AF: evaluate_subexp_c (c-lang.c:716)
==22159== by 0x6A8986: evaluate_subexp (eval.c:79)
==22159== by 0x6A8BA3: evaluate_type (eval.c:174)
==22159== by 0x817CCF: whatis_exp (typeprint.c:456)
==22159== by 0x817EAA: ptype_command (typeprint.c:508)
==22159== by 0x5F267B: do_cfunc (cli-decode.c:105)
==22159== by 0x5F5618: cmd_func (cli-decode.c:1885)
==22159== by 0x83622A: execute_command (top.c:475)
==22159== Address 0x8c6cb28 is 8 bytes inside a block of size 12 alloc'd
==22159== at 0x4C2AA98: calloc (vg_replace_malloc.c:711)
==22159== by 0x87384A: xcalloc (common-utils.c:83)
==22159== by 0x873889: xzalloc (common-utils.c:93)
==22159== by 0x6A34CB: allocate_value_contents (value.c:1036)
==22159== by 0x6A3501: allocate_value (value.c:1047)
==22159== by 0x6A790A: value_from_double (value.c:3656)
==22159== by 0x6A9F2D: evaluate_subexp_standard (eval.c:745)
==22159== by 0x7F31AF: evaluate_subexp_c (c-lang.c:716)
==22159== by 0x6A8986: evaluate_subexp (eval.c:79)
==22159== by 0x6A8BA3: evaluate_type (eval.c:174)
==22159== by 0x817CCF: whatis_exp (typeprint.c:456)
==22159== by 0x817EAA: ptype_command (typeprint.c:508)
==22159==
type = long double
(gdb)
Even if the target and host floating-point formats match, the length
of the types might still be different. On x86, long double is the
80-bit extended precision type on both 32-bit and 64-bit ABIs, but by
default it is stored as 12 bytes on 32-bit, and 16 bytes on 64-bit,
for alignment reasons. Several places in doublest.c already consider
this, but floatformat_to_doublest and floatformat_from_doublest miss
it. E.g., convert_typed_floating and store_typed_floating,
Tested on x86-64 Fedora 23 with --enable-libmcheck, where it fixes the
crashed above.
gdb/ChangeLog:
2016-03-09 Pedro Alves <palves@redhat.com>
* doublest.c: Extend comments.
(floatformat_to_doublest, floatformat_from_doublest): Copy the
floatformat's total size, not the host type's size.
-rw-r--r-- | gdb/ChangeLog | 6 | ||||
-rw-r--r-- | gdb/doublest.c | 30 |
2 files changed, 26 insertions, 10 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog index abb224d..1718ff1 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,5 +1,11 @@ 2016-03-09 Pedro Alves <palves@redhat.com> + * doublest.c: Extend comments. + (floatformat_to_doublest, floatformat_from_doublest): Copy the + floatformat's total size, not the host type's size. + +2016-03-09 Pedro Alves <palves@redhat.com> + * doublest.c (floatformat_totalsize_bytes): New function. (floatformat_from_type): Assert that the type's length is at least as long as the floatformat's totalsize. diff --git a/gdb/doublest.c b/gdb/doublest.c index fe06fec..088037e 100644 --- a/gdb/doublest.c +++ b/gdb/doublest.c @@ -686,7 +686,15 @@ floatformat_mantissa (const struct floatformat *fmt, If the host and target formats agree, we just copy the raw data into the appropriate type of variable and return, letting the host increase precision as necessary. Otherwise, we call the conversion - routine and let it do the dirty work. */ + routine and let it do the dirty work. Note that even if the target + and host floating-point formats match, the length of the types + might still be different, so the conversion routines must make sure + to not overrun any buffers. For example, on x86, long double is + the 80-bit extended precision type on both 32-bit and 64-bit ABIs, + but by default it is stored as 12 bytes on 32-bit, and 16 bytes on + 64-bit, for alignment reasons. See comment in store_typed_floating + for a discussion about zeroing out remaining bytes in the target + buffer. */ static const struct floatformat *host_float_format = GDB_HOST_FLOAT_FORMAT; static const struct floatformat *host_double_format = GDB_HOST_DOUBLE_FORMAT; @@ -707,25 +715,26 @@ floatformat_to_doublest (const struct floatformat *fmt, const void *in, DOUBLEST *out) { gdb_assert (fmt != NULL); + if (fmt == host_float_format) { - float val; + float val = 0; - memcpy (&val, in, sizeof (val)); + memcpy (&val, in, floatformat_totalsize_bytes (fmt)); *out = val; } else if (fmt == host_double_format) { - double val; + double val = 0; - memcpy (&val, in, sizeof (val)); + memcpy (&val, in, floatformat_totalsize_bytes (fmt)); *out = val; } else if (fmt == host_long_double_format) { - long double val; + long double val = 0; - memcpy (&val, in, sizeof (val)); + memcpy (&val, in, floatformat_totalsize_bytes (fmt)); *out = val; } else @@ -737,23 +746,24 @@ floatformat_from_doublest (const struct floatformat *fmt, const DOUBLEST *in, void *out) { gdb_assert (fmt != NULL); + if (fmt == host_float_format) { float val = *in; - memcpy (out, &val, sizeof (val)); + memcpy (out, &val, floatformat_totalsize_bytes (fmt)); } else if (fmt == host_double_format) { double val = *in; - memcpy (out, &val, sizeof (val)); + memcpy (out, &val, floatformat_totalsize_bytes (fmt)); } else if (fmt == host_long_double_format) { long double val = *in; - memcpy (out, &val, sizeof (val)); + memcpy (out, &val, floatformat_totalsize_bytes (fmt)); } else convert_doublest_to_floatformat (fmt, in, out); |