diff options
| author | Kouhei Sutou <[email protected]> | 2016-01-19 16:37:42 +0900 |
|---|---|---|
| committer | Kouhei Sutou <[email protected]> | 2016-01-19 16:37:42 +0900 |
| commit | 1d84b3205ae1b8bc5b8a269ffeb5a7f60e610ea6 (patch) | |
| tree | e410b5af1198dbed9ae7acd5741cd8af35e27abc /src/gc.c | |
| parent | 70d24a9e5f501d3f237605f1c48a0ebb934a0ce9 (diff) | |
| download | mruby-1d84b3205ae1b8bc5b8a269ffeb5a7f60e610ea6.tar.gz mruby-1d84b3205ae1b8bc5b8a269ffeb5a7f60e610ea6.zip | |
Fix SEGV on re-raising NoMemoryError
Think about the following Ruby script:
segv.rb:
begin
lambda do
lambda do
"x" * 1000 # NoMemoryError
end.call
end.call
rescue
raise
end
If memory can't allocate after `"x" * 1000`, mruby crashes.
Because L_RAISE: block in mrb_vm_exec() calls mrb_env_unshare() via
cipop() and mrb_env_unshare() uses allocated memory without NULL check:
L_RAISE: block:
L_RAISE:
// ...
while (ci[0].ridx == ci[-1].ridx) {
cipop(mrb);
// ...
}
cipop():
static void
cipop(mrb_state *mrb)
{
struct mrb_context *c = mrb->c;
if (c->ci->env) {
mrb_env_unshare(mrb, c->ci->env);
}
c->ci--;
}
mrb_env_unshare():
MRB_API void
mrb_env_unshare(mrb_state *mrb, struct REnv *e)
{
size_t len = (size_t)MRB_ENV_STACK_LEN(e);
// p is NULL in this case
mrb_value *p = (mrb_value *)mrb_malloc(mrb, sizeof(mrb_value)*len);
MRB_ENV_UNSHARE_STACK(e);
if (len > 0) {
stack_copy(p, e->stack, len); // p is NULL but used. It causes SEGV.
}
e->stack = p;
mrb_write_barrier(mrb, (struct RBasic *)e);
}
To solve the SEGV, this change always raises NoMemoryError even when
realloc() is failed after the first NoMemoryError in
mrb_realloc(). mrb_unv_unshare() doesn't need to check NULL with this
change.
But it causes infinite loop in the following while:
L_RAISE:
// ...
while (ci[0].ridx == ci[-1].ridx) {
cipop(mrb);
// ...
}
Because cipop() never pops ci.
This change includes cipop() change. The change pops ci even when
mrb_unv_unshare() is failed by NoMemoryError.
This case can be reproduced by the following program:
#include <stdlib.h>
#include <mruby.h>
#include <mruby/compile.h>
static void *
allocf(mrb_state *mrb, void *ptr, size_t size, void *ud)
{
static mrb_bool always_fail = FALSE;
if (size == 1001) {
always_fail = TRUE;
}
if (always_fail) {
return NULL;
}
if (size == 0) {
free(ptr);
return NULL;
} else {
return realloc(ptr, size);
}
}
int
main(int argc, char **argv)
{
mrb_state *mrb;
mrbc_context *c;
FILE *file;
mrb = mrb_open_allocf(allocf, NULL);
c = mrbc_context_new(mrb);
file = fopen(argv[1], "r");
mrb_load_file_cxt(mrb, file, c);
fclose(file);
mrbc_context_free(mrb, c);
mrb_close(mrb);
return EXIT_SUCCESS;
}
Try the following command lines:
% cc -I include -L build/host/lib -O0 -g3 -o no-memory no-memory.c -lmruby -lm
% ./no-memory segv.rb
Diffstat (limited to 'src/gc.c')
| -rw-r--r-- | src/gc.c | 1 |
1 files changed, 1 insertions, 0 deletions
@@ -215,6 +215,7 @@ mrb_realloc(mrb_state *mrb, void *p, size_t len) p2 = mrb_realloc_simple(mrb, p, len); if (!p2 && len) { if (mrb->gc.out_of_memory) { + mrb_exc_raise(mrb, mrb_obj_value(mrb->nomem_err)); /* mrb_panic(mrb); */ } else { |
