Skip to content

Conversation

@dearblue
Copy link
Contributor

The a, s, and z specifiers of mrb_get_args() allow you to get the address for an element of an object.

If the caller of mrb_get_args() subsequently calls mrb_vm_exec(), an operation may be performed that changes the address obtained. When the caller references the changed address in a subsequent operation, use-after-free is established.

Since this patch makes the address immutable, it will copy the object and freeze it if necessary. Note that the price of safety is a reduction in both memory usage and processing speed. If this is a concern, users are encouraged to use the A and S specifiers.

The `a`, `s`, and `z` specifiers of `mrb_get_args()` allow you to get the address for an element of an object.

If the caller of `mrb_get_args()` subsequently calls `mrb_vm_exec()`, an operation may be performed that changes the address obtained.
When the caller references the changed address in a subsequent operation, use-after-free is established.

Since this patch makes the address immutable, it will copy the object and freeze it if necessary.
Note that the price of safety is a reduction in both memory usage and processing speed.
If this is a concern, users are encouraged to use the `A` and `S` specifiers.
@dearblue dearblue requested a review from matz as a code owner September 19, 2024 14:22
@github-actions github-actions bot added the core label Sep 19, 2024
@dearblue
Copy link
Contributor Author

Use-after-free with the File.unlink method provided by mruby-io.

class Object
  RuntimeError_origin = RuntimeError
  remove_const :RuntimeError rescue nil
  remove_const :SystemCallError rescue nil

  def Object.const_missing(name)
    return super unless name == :RuntimeError
    $filename.clear
    RuntimeError_origin
  end
end

str = "msdosfs_iconv.ko"
$filename = "/boot/kernel/" << str
File.unlink $filename
% valgrind -- build/host/bin/mruby uaf-get-args-z.rb
==66746== Memcheck, a memory error detector
==66746== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
==66746== Using Valgrind-3.23.0 and LibVEX; rerun with -h for copyright info
==66746== Command: build/host/bin/mruby uaf-get-args-z.rb
==66746==
==66746== Invalid read of size 1
==66746==    at 0x4854150: strlen (vg_replace_strmem.c:519)
==66746==    by 0x28412D: mrb_str_new_cstr (string.c:197)
==66746==    by 0x26D964: mrb_raise (error.c:220)
==66746==    by 0x26E592: mrb_sys_fail (error.c:493)
==66746==    by 0x2EB476: mrb_file_s_unlink (file.c:132)
==66746==    by 0x2952EF: mrb_vm_exec (vm.c:0)
==66746==    by 0x2BA223: mrb_load_exec (parse.y:6912)
==66746==    by 0x2BA543: mrb_load_detect_file_cxt (parse.y:6955)
==66746==    by 0x260A23: main (mruby.c:353)
==66746==  Address 0x54e1ce0 is 0 bytes inside a block of size 55 free'd
==66746==    at 0x484F2EC: free (vg_replace_malloc.c:993)
==66746==    by 0x283DD8: mrb_default_allocf (allocf.c:22)
==66746==    by 0x28516F: str_replace (string.c:0)
==66746==    by 0x288AFF: mrb_str_replace (string.c:1970)
==66746==    by 0x2952EF: mrb_vm_exec (vm.c:0)
==66746==    by 0x28F546: mrb_run (vm.c:3058)
==66746==    by 0x28F546: mrb_funcall_with_block (vm.c:750)
==66746==    by 0x28D5D3: const_get (variable.c:793)
==66746==    by 0x2674C3: mrb_exc_get_id (class.c:662)
==66746==    by 0x26E584: mrb_sys_fail (error.c:493)
==66746==    by 0x2EB476: mrb_file_s_unlink (file.c:132)
==66746==    by 0x2952EF: mrb_vm_exec (vm.c:0)
==66746==    by 0x2BA223: mrb_load_exec (parse.y:6912)
==66746==  Block was alloc'd at
==66746==    at 0x4852321: realloc (vg_replace_malloc.c:1806)
==66746==    by 0x270428: mrb_realloc_simple (gc.c:197)
==66746==    by 0x270428: mrb_realloc (gc.c:211)
==66746==    by 0x270428: mrb_malloc (gc.c:227)
==66746==    by 0x286539: str_init_normal_capa (string.c:58)
==66746==    by 0x286539: resize_capa (string.c:174)
==66746==    by 0x286539: mrb_str_cat (string.c:2857)
==66746==    by 0x2D6343: str_concat (string.c:151)
==66746==    by 0x2D4E33: str_concat_m (string.c:174)
==66746==    by 0x2952EF: mrb_vm_exec (vm.c:0)
==66746==    by 0x2BA223: mrb_load_exec (parse.y:6912)
==66746==    by 0x2BA543: mrb_load_detect_file_cxt (parse.y:6955)
==66746==    by 0x260A23: main (mruby.c:353)
==66746==
==66746== Invalid read of size 1
==66746==    at 0x4854159: strlen (vg_replace_strmem.c:519)
==66746==    by 0x28412D: mrb_str_new_cstr (string.c:197)
==66746==    by 0x26D964: mrb_raise (error.c:220)
==66746==    by 0x26E592: mrb_sys_fail (error.c:493)
==66746==    by 0x2EB476: mrb_file_s_unlink (file.c:132)
==66746==    by 0x2952EF: mrb_vm_exec (vm.c:0)
==66746==    by 0x2BA223: mrb_load_exec (parse.y:6912)
==66746==    by 0x2BA543: mrb_load_detect_file_cxt (parse.y:6955)
==66746==    by 0x260A23: main (mruby.c:353)
==66746==  Address 0x54e1ce2 is 2 bytes inside a block of size 55 free'd
==66746==    at 0x484F2EC: free (vg_replace_malloc.c:993)
==66746==    by 0x283DD8: mrb_default_allocf (allocf.c:22)
==66746==    by 0x28516F: str_replace (string.c:0)
==66746==    by 0x288AFF: mrb_str_replace (string.c:1970)
==66746==    by 0x2952EF: mrb_vm_exec (vm.c:0)
==66746==    by 0x28F546: mrb_run (vm.c:3058)
==66746==    by 0x28F546: mrb_funcall_with_block (vm.c:750)
==66746==    by 0x28D5D3: const_get (variable.c:793)
==66746==    by 0x2674C3: mrb_exc_get_id (class.c:662)
==66746==    by 0x26E584: mrb_sys_fail (error.c:493)
==66746==    by 0x2EB476: mrb_file_s_unlink (file.c:132)
==66746==    by 0x2952EF: mrb_vm_exec (vm.c:0)
==66746==    by 0x2BA223: mrb_load_exec (parse.y:6912)
==66746==  Block was alloc'd at
==66746==    at 0x4852321: realloc (vg_replace_malloc.c:1806)
==66746==    by 0x270428: mrb_realloc_simple (gc.c:197)
==66746==    by 0x270428: mrb_realloc (gc.c:211)
==66746==    by 0x270428: mrb_malloc (gc.c:227)
==66746==    by 0x286539: str_init_normal_capa (string.c:58)
==66746==    by 0x286539: resize_capa (string.c:174)
==66746==    by 0x286539: mrb_str_cat (string.c:2857)
==66746==    by 0x2D6343: str_concat (string.c:151)
==66746==    by 0x2D4E33: str_concat_m (string.c:174)
==66746==    by 0x2952EF: mrb_vm_exec (vm.c:0)
==66746==    by 0x2BA223: mrb_load_exec (parse.y:6912)
==66746==    by 0x2BA543: mrb_load_detect_file_cxt (parse.y:6955)
==66746==    by 0x260A23: main (mruby.c:353)
==66746==
==66746== Invalid read of size 8
==66746==    at 0x4855670: memcpy (vg_replace_strmem.c:1167)
==66746==    by 0x2841D2: str_init_normal_capa (string.c:59)
==66746==    by 0x2841D2: str_init_normal (string.c:71)
==66746==    by 0x2841D2: str_new (string.c:152)
==66746==    by 0x2841D2: mrb_str_new_cstr (string.c:203)
==66746==    by 0x26D964: mrb_raise (error.c:220)
==66746==    by 0x26E592: mrb_sys_fail (error.c:493)
==66746==    by 0x2EB476: mrb_file_s_unlink (file.c:132)
==66746==    by 0x2952EF: mrb_vm_exec (vm.c:0)
==66746==    by 0x2BA223: mrb_load_exec (parse.y:6912)
==66746==    by 0x2BA543: mrb_load_detect_file_cxt (parse.y:6955)
==66746==    by 0x260A23: main (mruby.c:353)
==66746==  Address 0x54e1ce0 is 0 bytes inside a block of size 55 free'd
==66746==    at 0x484F2EC: free (vg_replace_malloc.c:993)
==66746==    by 0x283DD8: mrb_default_allocf (allocf.c:22)
==66746==    by 0x28516F: str_replace (string.c:0)
==66746==    by 0x288AFF: mrb_str_replace (string.c:1970)
==66746==    by 0x2952EF: mrb_vm_exec (vm.c:0)
==66746==    by 0x28F546: mrb_run (vm.c:3058)
==66746==    by 0x28F546: mrb_funcall_with_block (vm.c:750)
==66746==    by 0x28D5D3: const_get (variable.c:793)
==66746==    by 0x2674C3: mrb_exc_get_id (class.c:662)
==66746==    by 0x26E584: mrb_sys_fail (error.c:493)
==66746==    by 0x2EB476: mrb_file_s_unlink (file.c:132)
==66746==    by 0x2952EF: mrb_vm_exec (vm.c:0)
==66746==    by 0x2BA223: mrb_load_exec (parse.y:6912)
==66746==  Block was alloc'd at
==66746==    at 0x4852321: realloc (vg_replace_malloc.c:1806)
==66746==    by 0x270428: mrb_realloc_simple (gc.c:197)
==66746==    by 0x270428: mrb_realloc (gc.c:211)
==66746==    by 0x270428: mrb_malloc (gc.c:227)
==66746==    by 0x286539: str_init_normal_capa (string.c:58)
==66746==    by 0x286539: resize_capa (string.c:174)
==66746==    by 0x286539: mrb_str_cat (string.c:2857)
==66746==    by 0x2D6343: str_concat (string.c:151)
==66746==    by 0x2D4E33: str_concat_m (string.c:174)
==66746==    by 0x2952EF: mrb_vm_exec (vm.c:0)
==66746==    by 0x2BA223: mrb_load_exec (parse.y:6912)
==66746==    by 0x2BA543: mrb_load_detect_file_cxt (parse.y:6955)
==66746==    by 0x260A23: main (mruby.c:353)
==66746==
==66746== Invalid read of size 8
==66746==    at 0x485567F: memcpy (vg_replace_strmem.c:1167)
==66746==    by 0x2841D2: str_init_normal_capa (string.c:59)
==66746==    by 0x2841D2: str_init_normal (string.c:71)
==66746==    by 0x2841D2: str_new (string.c:152)
==66746==    by 0x2841D2: mrb_str_new_cstr (string.c:203)
==66746==    by 0x26D964: mrb_raise (error.c:220)
==66746==    by 0x26E592: mrb_sys_fail (error.c:493)
==66746==    by 0x2EB476: mrb_file_s_unlink (file.c:132)
==66746==    by 0x2952EF: mrb_vm_exec (vm.c:0)
==66746==    by 0x2BA223: mrb_load_exec (parse.y:6912)
==66746==    by 0x2BA543: mrb_load_detect_file_cxt (parse.y:6955)
==66746==    by 0x260A23: main (mruby.c:353)
==66746==  Address 0x54e1cf0 is 16 bytes inside a block of size 55 free'd
==66746==    at 0x484F2EC: free (vg_replace_malloc.c:993)
==66746==    by 0x283DD8: mrb_default_allocf (allocf.c:22)
==66746==    by 0x28516F: str_replace (string.c:0)
==66746==    by 0x288AFF: mrb_str_replace (string.c:1970)
==66746==    by 0x2952EF: mrb_vm_exec (vm.c:0)
==66746==    by 0x28F546: mrb_run (vm.c:3058)
==66746==    by 0x28F546: mrb_funcall_with_block (vm.c:750)
==66746==    by 0x28D5D3: const_get (variable.c:793)
==66746==    by 0x2674C3: mrb_exc_get_id (class.c:662)
==66746==    by 0x26E584: mrb_sys_fail (error.c:493)
==66746==    by 0x2EB476: mrb_file_s_unlink (file.c:132)
==66746==    by 0x2952EF: mrb_vm_exec (vm.c:0)
==66746==    by 0x2BA223: mrb_load_exec (parse.y:6912)
==66746==  Block was alloc'd at
==66746==    at 0x4852321: realloc (vg_replace_malloc.c:1806)
==66746==    by 0x270428: mrb_realloc_simple (gc.c:197)
==66746==    by 0x270428: mrb_realloc (gc.c:211)
==66746==    by 0x270428: mrb_malloc (gc.c:227)
==66746==    by 0x286539: str_init_normal_capa (string.c:58)
==66746==    by 0x286539: resize_capa (string.c:174)
==66746==    by 0x286539: mrb_str_cat (string.c:2857)
==66746==    by 0x2D6343: str_concat (string.c:151)
==66746==    by 0x2D4E33: str_concat_m (string.c:174)
==66746==    by 0x2952EF: mrb_vm_exec (vm.c:0)
==66746==    by 0x2BA223: mrb_load_exec (parse.y:6912)
==66746==    by 0x2BA543: mrb_load_detect_file_cxt (parse.y:6955)
==66746==    by 0x260A23: main (mruby.c:353)
==66746==
==66746== Invalid read of size 2
==66746==    at 0x4855710: memcpy (vg_replace_strmem.c:1167)
==66746==    by 0x2841D2: str_init_normal_capa (string.c:59)
==66746==    by 0x2841D2: str_init_normal (string.c:71)
==66746==    by 0x2841D2: str_new (string.c:152)
==66746==    by 0x2841D2: mrb_str_new_cstr (string.c:203)
==66746==    by 0x26D964: mrb_raise (error.c:220)
==66746==    by 0x26E592: mrb_sys_fail (error.c:493)
==66746==    by 0x2EB476: mrb_file_s_unlink (file.c:132)
==66746==    by 0x2952EF: mrb_vm_exec (vm.c:0)
==66746==    by 0x2BA223: mrb_load_exec (parse.y:6912)
==66746==    by 0x2BA543: mrb_load_detect_file_cxt (parse.y:6955)
==66746==    by 0x260A23: main (mruby.c:353)
==66746==  Address 0x54e1cf8 is 24 bytes inside a block of size 55 free'd
==66746==    at 0x484F2EC: free (vg_replace_malloc.c:993)
==66746==    by 0x283DD8: mrb_default_allocf (allocf.c:22)
==66746==    by 0x28516F: str_replace (string.c:0)
==66746==    by 0x288AFF: mrb_str_replace (string.c:1970)
==66746==    by 0x2952EF: mrb_vm_exec (vm.c:0)
==66746==    by 0x28F546: mrb_run (vm.c:3058)
==66746==    by 0x28F546: mrb_funcall_with_block (vm.c:750)
==66746==    by 0x28D5D3: const_get (variable.c:793)
==66746==    by 0x2674C3: mrb_exc_get_id (class.c:662)
==66746==    by 0x26E584: mrb_sys_fail (error.c:493)
==66746==    by 0x2EB476: mrb_file_s_unlink (file.c:132)
==66746==    by 0x2952EF: mrb_vm_exec (vm.c:0)
==66746==    by 0x2BA223: mrb_load_exec (parse.y:6912)
==66746==  Block was alloc'd at
==66746==    at 0x4852321: realloc (vg_replace_malloc.c:1806)
==66746==    by 0x270428: mrb_realloc_simple (gc.c:197)
==66746==    by 0x270428: mrb_realloc (gc.c:211)
==66746==    by 0x270428: mrb_malloc (gc.c:227)
==66746==    by 0x286539: str_init_normal_capa (string.c:58)
==66746==    by 0x286539: resize_capa (string.c:174)
==66746==    by 0x286539: mrb_str_cat (string.c:2857)
==66746==    by 0x2D6343: str_concat (string.c:151)
==66746==    by 0x2D4E33: str_concat_m (string.c:174)
==66746==    by 0x2952EF: mrb_vm_exec (vm.c:0)
==66746==    by 0x2BA223: mrb_load_exec (parse.y:6912)
==66746==    by 0x2BA543: mrb_load_detect_file_cxt (parse.y:6955)
==66746==    by 0x260A23: main (mruby.c:353)
==66746==
==66746== Invalid read of size 1
==66746==    at 0x48557A0: memcpy (vg_replace_strmem.c:1167)
==66746==    by 0x2841D2: str_init_normal_capa (string.c:59)
==66746==    by 0x2841D2: str_init_normal (string.c:71)
==66746==    by 0x2841D2: str_new (string.c:152)
==66746==    by 0x2841D2: mrb_str_new_cstr (string.c:203)
==66746==    by 0x26D964: mrb_raise (error.c:220)
==66746==    by 0x26E592: mrb_sys_fail (error.c:493)
==66746==    by 0x2EB476: mrb_file_s_unlink (file.c:132)
==66746==    by 0x2952EF: mrb_vm_exec (vm.c:0)
==66746==    by 0x2BA223: mrb_load_exec (parse.y:6912)
==66746==    by 0x2BA543: mrb_load_detect_file_cxt (parse.y:6955)
==66746==    by 0x260A23: main (mruby.c:353)
==66746==  Address 0x54e1cfc is 28 bytes inside a block of size 55 free'd
==66746==    at 0x484F2EC: free (vg_replace_malloc.c:993)
==66746==    by 0x283DD8: mrb_default_allocf (allocf.c:22)
==66746==    by 0x28516F: str_replace (string.c:0)
==66746==    by 0x288AFF: mrb_str_replace (string.c:1970)
==66746==    by 0x2952EF: mrb_vm_exec (vm.c:0)
==66746==    by 0x28F546: mrb_run (vm.c:3058)
==66746==    by 0x28F546: mrb_funcall_with_block (vm.c:750)
==66746==    by 0x28D5D3: const_get (variable.c:793)
==66746==    by 0x2674C3: mrb_exc_get_id (class.c:662)
==66746==    by 0x26E584: mrb_sys_fail (error.c:493)
==66746==    by 0x2EB476: mrb_file_s_unlink (file.c:132)
==66746==    by 0x2952EF: mrb_vm_exec (vm.c:0)
==66746==    by 0x2BA223: mrb_load_exec (parse.y:6912)
==66746==  Block was alloc'd at
==66746==    at 0x4852321: realloc (vg_replace_malloc.c:1806)
==66746==    by 0x270428: mrb_realloc_simple (gc.c:197)
==66746==    by 0x270428: mrb_realloc (gc.c:211)
==66746==    by 0x270428: mrb_malloc (gc.c:227)
==66746==    by 0x286539: str_init_normal_capa (string.c:58)
==66746==    by 0x286539: resize_capa (string.c:174)
==66746==    by 0x286539: mrb_str_cat (string.c:2857)
==66746==    by 0x2D6343: str_concat (string.c:151)
==66746==    by 0x2D4E33: str_concat_m (string.c:174)
==66746==    by 0x2952EF: mrb_vm_exec (vm.c:0)
==66746==    by 0x2BA223: mrb_load_exec (parse.y:6912)
==66746==    by 0x2BA543: mrb_load_detect_file_cxt (parse.y:6955)
==66746==    by 0x260A23: main (mruby.c:353)
==66746==
trace (most recent call last):
        [1] uaf-get-args-z.rb:17
uaf-get-args-z.rb:17:in unlink: /boot/kernel/msdosfs_iconv.ko (RuntimeError)
==66746==
==66746== HEAP SUMMARY:
==66746==     in use at exit: 0 bytes in 0 blocks
==66746==   total heap usage: 796 allocs, 796 frees, 231,122 bytes allocated
==66746==
==66746== All heap blocks were freed -- no leaks are possible
==66746==
==66746== For lists of detected and suppressed errors, rerun with: -s
==66746== ERROR SUMMARY: 36 errors from 6 contexts (suppressed: 0 from 0)

@dearblue
Copy link
Contributor Author

The demonstration example shown in #6357 (comment) can also be resolved by merging #6359.
If this PR is rejected, a note should be added to the comments as documentation for mrb_get_args().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant