From 14a17063a11a01d518b4bbaf0eb967330aec3984 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 3 Dec 2019 08:12:57 +0900 Subject: Fixed stack overflow [Bug #16382] Get rid of infinite recursion in expanding a load path to the real path while loading a transcoder. --- file.c | 33 ++++++++++++++++----------------- internal.h | 4 +++- load.c | 2 +- test/ruby/test_encoding.rb | 11 +++++++++++ 4 files changed, 31 insertions(+), 19 deletions(-) diff --git a/file.c b/file.c index 05cdf51e77..5644656b44 100644 --- a/file.c +++ b/file.c @@ -242,7 +242,7 @@ rb_str_encode_ospath(VALUE path) encidx = rb_filesystem_encindex(); } #endif - if (encidx != ENCINDEX_UTF_8) { + if (encidx != ENCINDEX_ASCII && encidx != ENCINDEX_UTF_8) { rb_encoding *enc = rb_enc_from_index(encidx); rb_encoding *utf8 = rb_utf8_encoding(); path = rb_str_conv_enc(path, enc, utf8); @@ -4243,7 +4243,7 @@ realpath_rec(long *prefixlenp, VALUE *resolvedp, const char *unresolved, VALUE f } static VALUE -rb_check_realpath_emulate(VALUE basedir, VALUE path, enum rb_realpath_mode mode) +rb_check_realpath_emulate(VALUE basedir, VALUE path, rb_encoding *origenc, enum rb_realpath_mode mode) { long prefixlen; VALUE resolved; @@ -4251,7 +4251,7 @@ rb_check_realpath_emulate(VALUE basedir, VALUE path, enum rb_realpath_mode mode) VALUE loopcheck; VALUE curdir = Qnil; - rb_encoding *enc, *origenc; + rb_encoding *enc; char *path_names = NULL, *basedir_names = NULL, *curdir_names = NULL; char *ptr, *prefixptr = NULL, *pend; long len; @@ -4264,7 +4264,6 @@ rb_check_realpath_emulate(VALUE basedir, VALUE path, enum rb_realpath_mode mode) } enc = rb_enc_get(unresolved_path); - origenc = enc; unresolved_path = TO_OSPATH(unresolved_path); RSTRING_GETMEM(unresolved_path, ptr, len); path_names = skipprefixroot(ptr, ptr + len, rb_enc_get(unresolved_path)); @@ -4322,7 +4321,7 @@ rb_check_realpath_emulate(VALUE basedir, VALUE path, enum rb_realpath_mode mode) if (realpath_rec(&prefixlen, &resolved, path_names, Qnil, loopcheck, mode, 1)) return Qnil; - if (origenc != rb_enc_get(resolved)) { + if (origenc && origenc != rb_enc_get(resolved)) { if (rb_enc_str_asciionly_p(resolved)) { rb_enc_associate(resolved, origenc); } @@ -4339,25 +4338,23 @@ rb_check_realpath_emulate(VALUE basedir, VALUE path, enum rb_realpath_mode mode) static VALUE rb_file_join(VALUE ary); static VALUE -rb_check_realpath_internal(VALUE basedir, VALUE path, enum rb_realpath_mode mode) +rb_check_realpath_internal(VALUE basedir, VALUE path, rb_encoding *origenc, enum rb_realpath_mode mode) { #ifdef HAVE_REALPATH VALUE unresolved_path; - rb_encoding *origenc; char *resolved_ptr = NULL; VALUE resolved; struct stat st; if (mode == RB_REALPATH_DIR) { - return rb_check_realpath_emulate(basedir, path, mode); + return rb_check_realpath_emulate(basedir, path, origenc, mode); } unresolved_path = rb_str_dup_frozen(path); - origenc = rb_enc_get(unresolved_path); if (*RSTRING_PTR(unresolved_path) != '/' && !NIL_P(basedir)) { unresolved_path = rb_file_join(rb_assoc_new(basedir, unresolved_path)); } - unresolved_path = TO_OSPATH(unresolved_path); + if (origenc) unresolved_path = TO_OSPATH(unresolved_path); if ((resolved_ptr = realpath(RSTRING_PTR(unresolved_path), NULL)) == NULL) { /* glibc realpath(3) does not allow /path/to/file.rb/../other_file.rb, @@ -4367,7 +4364,7 @@ rb_check_realpath_internal(VALUE basedir, VALUE path, enum rb_realpath_mode mode Fallback to the emulated approach in either of those cases. */ if (errno == ENOTDIR || (errno == ENOENT && rb_file_exist_p(0, unresolved_path))) { - return rb_check_realpath_emulate(basedir, path, mode); + return rb_check_realpath_emulate(basedir, path, origenc, mode); } if (mode == RB_REALPATH_CHECK) { @@ -4378,14 +4375,16 @@ rb_check_realpath_internal(VALUE basedir, VALUE path, enum rb_realpath_mode mode resolved = ospath_new(resolved_ptr, strlen(resolved_ptr), rb_filesystem_encoding()); free(resolved_ptr); - if (rb_stat(resolved, &st) < 0) { + /* As `resolved` is a String in the filesystem encoding, no + * conversion is needed */ + if (stat_without_gvl(RSTRING_PTR(resolved), &st) < 0) { if (mode == RB_REALPATH_CHECK) { return Qnil; } rb_sys_fail_path(unresolved_path); } - if (origenc != rb_enc_get(resolved)) { + if (origenc && origenc != rb_enc_get(resolved)) { if (!rb_enc_str_asciionly_p(resolved)) { resolved = rb_str_conv_enc(resolved, NULL, origenc); } @@ -4402,7 +4401,7 @@ rb_check_realpath_internal(VALUE basedir, VALUE path, enum rb_realpath_mode mode RB_GC_GUARD(unresolved_path); return resolved; #else - return rb_check_realpath_emulate(basedir, path, mode); + return rb_check_realpath_emulate(basedir, path, origenc, mode); #endif /* HAVE_REALPATH */ } @@ -4411,13 +4410,13 @@ rb_realpath_internal(VALUE basedir, VALUE path, int strict) { const enum rb_realpath_mode mode = strict ? RB_REALPATH_STRICT : RB_REALPATH_DIR; - return rb_check_realpath_internal(basedir, path, mode); + return rb_check_realpath_internal(basedir, path, rb_enc_get(path), mode); } VALUE -rb_check_realpath(VALUE basedir, VALUE path) +rb_check_realpath(VALUE basedir, VALUE path, rb_encoding *enc) { - return rb_check_realpath_internal(basedir, path, RB_REALPATH_CHECK); + return rb_check_realpath_internal(basedir, path, enc, RB_REALPATH_CHECK); } /* diff --git a/internal.h b/internal.h index ca635c9e36..77101d0195 100644 --- a/internal.h +++ b/internal.h @@ -1583,7 +1583,9 @@ extern const char ruby_null_device[]; VALUE rb_home_dir_of(VALUE user, VALUE result); VALUE rb_default_home_dir(VALUE result); VALUE rb_realpath_internal(VALUE basedir, VALUE path, int strict); -VALUE rb_check_realpath(VALUE basedir, VALUE path); +#ifdef RUBY_ENCODING_H +VALUE rb_check_realpath(VALUE basedir, VALUE path, rb_encoding *origenc); +#endif void rb_file_const(const char*, VALUE); int rb_file_load_ok(const char *); VALUE rb_file_expand_path_fast(VALUE, VALUE); diff --git a/load.c b/load.c index 7772e746f9..fda100fb1a 100644 --- a/load.c +++ b/load.c @@ -79,7 +79,7 @@ rb_construct_expanded_load_path(enum expand_type type, int *has_relative, int *h if (is_string) rb_str_freeze(path); as_str = rb_get_path_check_convert(as_str); - expanded_path = rb_check_realpath(Qnil, as_str); + expanded_path = rb_check_realpath(Qnil, as_str, NULL); if (NIL_P(expanded_path)) expanded_path = as_str; rb_ary_push(ary, rb_fstring(expanded_path)); } diff --git a/test/ruby/test_encoding.rb b/test/ruby/test_encoding.rb index 40fd302c07..019cb2417f 100644 --- a/test/ruby/test_encoding.rb +++ b/test/ruby/test_encoding.rb @@ -123,4 +123,15 @@ class TestEncoding < Test::Unit::TestCase assert_include(e.message, "/regexp/sQ\n") end; end + + def test_nonascii_library_path + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}".force_encoding("US-ASCII")) + begin; + assert_equal(Encoding::US_ASCII, __ENCODING__) + $:.unshift("/\x80") + assert_raise_with_message(LoadError, /\[Bug #16382\]/) do + $:.resolve_feature_path "[Bug #16382]" + end + end; + end end -- cgit v1.2.3