[RFA] PR gdb/17210 - fix possible memory leak in read_memory_robust

Message ID 1465490013-15336-1-git-send-email-tom@tromey.com
State New, archived
Headers

Commit Message

Tom Tromey June 9, 2016, 4:33 p.m. UTC
  PR gdb/17210 concerns a possible memory leak in read_memory_robust.
The bug can happen because read_memory_robust allocates memory, does
not install any cleanups, and invokes QUIT.  Similarly, it target_read
calls QUIT, so it too can potentially throw.

The fix is to install cleanups to guard the allocated memory.

Built and regtested on x86-64 Fedora 23.  I couldn't think of a way to
test this, so no new test; and of course this means it should have
more careful review.

2016-06-09  Tom Tromey  <tom@tromey.com>

	PR gdb/17210:
	* target.c (free_memory_read_result_vector): Take a pointer to the
	VEC as an argument.
	(read_memory_robust): Install a cleanup for "result".
	* mi/mi-main.c (mi_cmd_data_read_memory_bytes): Update.
---
 gdb/ChangeLog    |  8 ++++++++
 gdb/mi/mi-main.c |  2 +-
 gdb/target.c     | 15 +++++++++++----
 3 files changed, 20 insertions(+), 5 deletions(-)
  

Comments

Yao Qi June 28, 2016, 10:42 a.m. UTC | #1
On Thu, Jun 9, 2016 at 5:33 PM, Tom Tromey <tom@tromey.com> wrote:
>
>  VEC(memory_read_result_s) *
> @@ -1810,6 +1810,8 @@ read_memory_robust (struct target_ops *ops,
>  {
>    VEC(memory_read_result_s) *result = 0;
>    int unit_size = gdbarch_addressable_memory_unit_size (target_gdbarch ());
> +  struct cleanup *cleanup = make_cleanup (free_memory_read_result_vector,
> +                                         &result);
>

result is a local variable on stack, so its address is meaningless when the
exception is throw, because the stack has already been destroyed.

Probably, we can register cleanup for result once it becomes to non-NULL,
and changes in free_memory_read_result_vector are not needed.
  
Tom Tromey June 28, 2016, 2:39 p.m. UTC | #2
>>>>> "Yao" == Yao Qi <qiyaoltc@gmail.com> writes:

Yao> On Thu, Jun 9, 2016 at 5:33 PM, Tom Tromey <tom@tromey.com> wrote:
>> 
>> VEC(memory_read_result_s) *
>> @@ -1810,6 +1810,8 @@ read_memory_robust (struct target_ops *ops,
>> {
>> VEC(memory_read_result_s) *result = 0;
>> int unit_size = gdbarch_addressable_memory_unit_size (target_gdbarch ());
>> +  struct cleanup *cleanup = make_cleanup (free_memory_read_result_vector,
>> +                                         &result);
>> 

Yao> result is a local variable on stack, so its address is meaningless when the
Yao> exception is throw, because the stack has already been destroyed.

Yao> Probably, we can register cleanup for result once it becomes to non-NULL,
Yao> and changes in free_memory_read_result_vector are not needed.

I don't think that will work, because resizing the vector may cause the
value to change.  Though one option would be to discard the cleanup and
recreate it after each push.

Tom
  
Pedro Alves June 28, 2016, 5:47 p.m. UTC | #3
On 06/28/2016 11:42 AM, Yao Qi wrote:
> On Thu, Jun 9, 2016 at 5:33 PM, Tom Tromey <tom@tromey.com> wrote:
>>
>>  VEC(memory_read_result_s) *
>> @@ -1810,6 +1810,8 @@ read_memory_robust (struct target_ops *ops,
>>  {
>>    VEC(memory_read_result_s) *result = 0;
>>    int unit_size = gdbarch_addressable_memory_unit_size (target_gdbarch ());
>> +  struct cleanup *cleanup = make_cleanup (free_memory_read_result_vector,
>> +                                         &result);
>>
> 
> result is a local variable on stack, so its address is meaningless when the
> exception is throw, because the stack has already been destroyed.

Can you clarify?
Cleanups do run before the stack is destroyed.  See most 
free_current_contents users.

Thanks,
Pedro Alves
  
Yao Qi June 29, 2016, 9:27 a.m. UTC | #4
On Tue, Jun 28, 2016 at 6:47 PM, Pedro Alves <palves@redhat.com> wrote:
> On 06/28/2016 11:42 AM, Yao Qi wrote:
>> On Thu, Jun 9, 2016 at 5:33 PM, Tom Tromey <tom@tromey.com> wrote:
>>>
>>>  VEC(memory_read_result_s) *
>>> @@ -1810,6 +1810,8 @@ read_memory_robust (struct target_ops *ops,
>>>  {
>>>    VEC(memory_read_result_s) *result = 0;
>>>    int unit_size = gdbarch_addressable_memory_unit_size (target_gdbarch ());
>>> +  struct cleanup *cleanup = make_cleanup (free_memory_read_result_vector,
>>> +                                         &result);
>>>
>>
>> result is a local variable on stack, so its address is meaningless when the
>> exception is throw, because the stack has already been destroyed.
>
> Can you clarify?
> Cleanups do run before the stack is destroyed.  See most
> free_current_contents users.

Urr.. right... do_cleanups is called when the exception is thrown
where the stack
is not destroyed yet.  I thought do_cleanups is called when gdb goes back to
the top level in this case.

Tom, the patch is good to me then.
  

Patch

diff --git a/gdb/ChangeLog b/gdb/ChangeLog
index 9c09269..dc1c908 100644
--- a/gdb/ChangeLog
+++ b/gdb/ChangeLog
@@ -1,3 +1,11 @@ 
+2016-06-09  Tom Tromey  <tom@tromey.com>
+
+	PR gdb/17210:
+	* target.c (free_memory_read_result_vector): Take a pointer to the
+	VEC as an argument.
+	(read_memory_robust): Install a cleanup for "result".
+	* mi/mi-main.c (mi_cmd_data_read_memory_bytes): Update.
+
 2016-06-07  Simon Marchi  <simon.marchi@ericsson.com>
 
 	* mi/mi-interp.c (mi_record_changed): Add missing braces.
diff --git a/gdb/mi/mi-main.c b/gdb/mi/mi-main.c
index d53bcc7..0e048f3 100644
--- a/gdb/mi/mi-main.c
+++ b/gdb/mi/mi-main.c
@@ -1635,7 +1635,7 @@  mi_cmd_data_read_memory_bytes (char *command, char **argv, int argc)
 
   result = read_memory_robust (current_target.beneath, addr, length);
 
-  cleanups = make_cleanup (free_memory_read_result_vector, result);
+  cleanups = make_cleanup (free_memory_read_result_vector, &result);
 
   if (VEC_length (memory_read_result_s, result) == 0)
     error (_("Unable to read memory."));
diff --git a/gdb/target.c b/gdb/target.c
index c0ce46d..442d632 100644
--- a/gdb/target.c
+++ b/gdb/target.c
@@ -1793,15 +1793,15 @@  read_whatever_is_readable (struct target_ops *ops,
 void
 free_memory_read_result_vector (void *x)
 {
-  VEC(memory_read_result_s) *v = (VEC(memory_read_result_s) *) x;
+  VEC(memory_read_result_s) **v = (VEC(memory_read_result_s) **) x;
   memory_read_result_s *current;
   int ix;
 
-  for (ix = 0; VEC_iterate (memory_read_result_s, v, ix, current); ++ix)
+  for (ix = 0; VEC_iterate (memory_read_result_s, *v, ix, current); ++ix)
     {
       xfree (current->data);
     }
-  VEC_free (memory_read_result_s, v);
+  VEC_free (memory_read_result_s, *v);
 }
 
 VEC(memory_read_result_s) *
@@ -1810,6 +1810,8 @@  read_memory_robust (struct target_ops *ops,
 {
   VEC(memory_read_result_s) *result = 0;
   int unit_size = gdbarch_addressable_memory_unit_size (target_gdbarch ());
+  struct cleanup *cleanup = make_cleanup (free_memory_read_result_vector,
+					  &result);
 
   LONGEST xfered_total = 0;
   while (xfered_total < len)
@@ -1836,6 +1838,7 @@  read_memory_robust (struct target_ops *ops,
 	{
 	  LONGEST to_read = min (len - xfered_total, region_len);
 	  gdb_byte *buffer = (gdb_byte *) xmalloc (to_read * unit_size);
+	  struct cleanup *inner_cleanup = make_cleanup (xfree, buffer);
 
 	  LONGEST xfered_partial =
 	      target_read (ops, TARGET_OBJECT_MEMORY, NULL,
@@ -1846,7 +1849,7 @@  read_memory_robust (struct target_ops *ops,
 	    {
 	      /* Got an error reading full chunk.  See if maybe we can read
 		 some subrange.  */
-	      xfree (buffer);
+	      do_cleanups (inner_cleanup);
 	      read_whatever_is_readable (ops, offset + xfered_total,
 					 offset + xfered_total + to_read,
 					 unit_size, &result);
@@ -1855,6 +1858,8 @@  read_memory_robust (struct target_ops *ops,
 	  else
 	    {
 	      struct memory_read_result r;
+
+	      discard_cleanups (inner_cleanup);
 	      r.data = buffer;
 	      r.begin = offset + xfered_total;
 	      r.end = r.begin + xfered_partial;
@@ -1864,6 +1869,8 @@  read_memory_robust (struct target_ops *ops,
 	  QUIT;
 	}
     }
+
+  discard_cleanups (cleanup);
   return result;
 }