Re: Backport patch for realpath(3) usage for File.realpath to Ruby 2.4-2.6 to work with unveil(2)
On 07/01 12:34, Jeremy Evans wrote: > Ruby previously had an emulated approach for File.realpath, which did > not work correctly when using unveil(2). This backports a patch to > use realpath(3) for File.realpath that I recently committed upstream. > > I have tested this works as expected with unveil(2) on -current, and > have been running it on some personal apps for about a week to serve > Ruby web applications using unveil(2) instead of chroot(2) to limit file > system access. unveil(2) is a lot less fragile than chroot(2) for > limiting file system access in Ruby web applications, because many Ruby > libraries have an unfortunate tendency to load Ruby code at runtime from > locations under /usr/local/lib/ruby due to a misfeature called autoload. > > Regen patches while here. > > I plan to commit this in a couple days unless I hear objections. Looks like I forgot to commit this in July. I've been running it since then with no problems. Today, new versions of Ruby 2.4, 2.5, and 2.6 were released to fix a minor issue in RDoc due to an embedded copy of JQuery. Release announcements at: https://www.ruby-lang.org/en/news/2019/08/28/ruby-2-6-4-released/ https://www.ruby-lang.org/en/news/2019/08/28/ruby-2-5-6-released/ https://www.ruby-lang.org/en/news/2019/08/28/ruby-2-4-7-released/ I'm going to include the File.realpath patch with this version update. Ports-wise, this drops the PATCHFILES usage in ruby 2.6, as the patch is included in 2.6.4. It also regens patches. Tested on amd64. I plan to commit this in a couple days unless I hear objections. After that, I'll update 6.5-stable to get the security fix, but I will not be including the File.realpath patch in the -stable update. Thanks, Jeremy Index: 2.4/Makefile === RCS file: /cvs/ports/lang/ruby/2.4/Makefile,v retrieving revision 1.17 diff -u -p -r1.17 Makefile --- 2.4/Makefile25 Jun 2019 20:25:21 - 1.17 +++ 2.4/Makefile28 Aug 2019 18:00:33 - @@ -1,7 +1,6 @@ # $OpenBSD: Makefile,v 1.17 2019/06/25 20:25:21 sthen Exp $ -VERSION = 2.4.6 -REVISION-main =0 +VERSION = 2.4.7 SHARED_LIBS = ruby24 2.0 NEXTVER = 2.5 Index: 2.4/distinfo === RCS file: /cvs/ports/lang/ruby/2.4/distinfo,v retrieving revision 1.10 diff -u -p -r1.10 distinfo --- 2.4/distinfo3 Apr 2019 17:25:25 - 1.10 +++ 2.4/distinfo28 Aug 2019 18:00:33 - @@ -1,2 +1,2 @@ -SHA256 (ruby-2.4.6.tar.gz) = 3g3ICXAjcWCZ98im/8dRURuQ3n9WlPQBtZ8tBx25EL4= -SIZE (ruby-2.4.6.tar.gz) = 15880585 +SHA256 (ruby-2.4.7.tar.gz) = zW78cgympiJ0XiusefRebNY6sPWlOtfriBVF9Y/zi4k= +SIZE (ruby-2.4.7.tar.gz) = 16036496 Index: 2.4/patches/patch-file_c === RCS file: 2.4/patches/patch-file_c diff -N 2.4/patches/patch-file_c --- /dev/null 1 Jan 1970 00:00:00 - +++ 2.4/patches/patch-file_c28 Aug 2019 18:00:33 - @@ -0,0 +1,102 @@ +$OpenBSD$ + +Backport use of realpath(3) for File.realpath to allow unveil(2) to work. + +Index: file.c +--- file.c.orig file.c +@@ -126,6 +126,9 @@ int flock(int, int); + #define STAT(p, s)stat((p), (s)) + #endif + ++#include ++#include ++ + VALUE rb_cFile; + VALUE rb_mFileTest; + VALUE rb_cStat; +@@ -3898,7 +3901,7 @@ realpath_rec(long *prefixlenp, VALUE *resolvedp, const + } + + static VALUE +-rb_check_realpath_internal(VALUE basedir, VALUE path, enum rb_realpath_mode mode) ++rb_check_realpath_emulate(VALUE basedir, VALUE path, enum rb_realpath_mode mode) + { + long prefixlen; + VALUE resolved; +@@ -3980,6 +3983,75 @@ rb_check_realpath_internal(VALUE basedir, VALUE path, + rb_enc_associate(resolved, origenc); + + OBJ_INFECT(resolved, unresolved_path); ++return resolved; ++} ++ ++static VALUE rb_file_join(VALUE ary, VALUE sep); ++ ++static VALUE ++rb_check_realpath_internal(VALUE basedir, VALUE path, enum rb_realpath_mode mode) ++{ ++VALUE unresolved_path; ++rb_encoding *origenc; ++char *resolved_ptr = NULL; ++VALUE resolved; ++ ++if (mode == RB_REALPATH_DIR) { ++ return rb_check_realpath_emulate(basedir, path, 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_ary_new_from_args(2, basedir, unresolved_path), rb_str_new2("/")); ++} ++ ++if((resolved_ptr = realpath(RSTRING_PTR(unresolved_path), NULL)) == NULL) { ++ /* glibc realpath(3) does not allow /path/to/file.rb/../other_file.rb, ++ returning ENOTDIR in that case. ++ glibc realpath(3) can also return ENOENT for paths that exist, ++ such as /dev/fd/5. ++ Fallback to the emulated approach in either of those cases. */ ++ if
Backport patch for realpath(3) usage for File.realpath to Ruby 2.4-2.6 to work with unveil(2)
Ruby previously had an emulated approach for File.realpath, which did not work correctly when using unveil(2). This backports a patch to use realpath(3) for File.realpath that I recently committed upstream. I have tested this works as expected with unveil(2) on -current, and have been running it on some personal apps for about a week to serve Ruby web applications using unveil(2) instead of chroot(2) to limit file system access. unveil(2) is a lot less fragile than chroot(2) for limiting file system access in Ruby web applications, because many Ruby libraries have an unfortunate tendency to load Ruby code at runtime from locations under /usr/local/lib/ruby due to a misfeature called autoload. Regen patches while here. I plan to commit this in a couple days unless I hear objections. Thanks, Jeremy Index: 2.4/Makefile === RCS file: /cvs/ports/lang/ruby/2.4/Makefile,v retrieving revision 1.16 diff -u -p -r1.16 Makefile --- 2.4/Makefile3 Apr 2019 17:25:25 - 1.16 +++ 2.4/Makefile26 Jun 2019 19:45:33 - @@ -3,6 +3,7 @@ VERSION = 2.4.6 SHARED_LIBS = ruby24 2.0 NEXTVER = 2.5 +REVISION-main =0 PSEUDO_FLAVORS=no_ri_docs bootstrap # Do not build the RI docs on slow arches Index: 2.4/patches/patch-file_c === RCS file: 2.4/patches/patch-file_c diff -N 2.4/patches/patch-file_c --- /dev/null 1 Jan 1970 00:00:00 - +++ 2.4/patches/patch-file_c26 Jun 2019 20:11:48 - @@ -0,0 +1,102 @@ +$OpenBSD$ + +Backport use of realpath(3) for File.realpath to allow unveil(2) to work. + +Index: file.c +--- file.c.orig file.c +@@ -126,6 +126,9 @@ int flock(int, int); + #define STAT(p, s)stat((p), (s)) + #endif + ++#include ++#include ++ + VALUE rb_cFile; + VALUE rb_mFileTest; + VALUE rb_cStat; +@@ -3898,7 +3901,7 @@ realpath_rec(long *prefixlenp, VALUE *resolvedp, const + } + + static VALUE +-rb_check_realpath_internal(VALUE basedir, VALUE path, enum rb_realpath_mode mode) ++rb_check_realpath_emulate(VALUE basedir, VALUE path, enum rb_realpath_mode mode) + { + long prefixlen; + VALUE resolved; +@@ -3980,6 +3983,75 @@ rb_check_realpath_internal(VALUE basedir, VALUE path, + rb_enc_associate(resolved, origenc); + + OBJ_INFECT(resolved, unresolved_path); ++return resolved; ++} ++ ++static VALUE rb_file_join(VALUE ary, VALUE sep); ++ ++static VALUE ++rb_check_realpath_internal(VALUE basedir, VALUE path, enum rb_realpath_mode mode) ++{ ++VALUE unresolved_path; ++rb_encoding *origenc; ++char *resolved_ptr = NULL; ++VALUE resolved; ++ ++if (mode == RB_REALPATH_DIR) { ++ return rb_check_realpath_emulate(basedir, path, 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_ary_new_from_args(2, basedir, unresolved_path), rb_str_new2("/")); ++} ++ ++if((resolved_ptr = realpath(RSTRING_PTR(unresolved_path), NULL)) == NULL) { ++ /* glibc realpath(3) does not allow /path/to/file.rb/../other_file.rb, ++ returning ENOTDIR in that case. ++ glibc realpath(3) can also return ENOENT for paths that exist, ++ such as /dev/fd/5. ++ 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); ++ ++ } ++ if (mode == RB_REALPATH_CHECK) { ++ return Qnil; ++ } ++ rb_sys_fail_path(unresolved_path); ++} ++resolved = ospath_new(resolved_ptr, strlen(resolved_ptr), rb_filesystem_encoding()); ++free(resolved_ptr); ++ ++if (mode == RB_REALPATH_STRICT || mode == RB_REALPATH_CHECK) { ++ struct stat st; ++ ++ if (rb_stat(resolved, ) < 0) { ++ if (mode == RB_REALPATH_STRICT) { ++ rb_sys_fail_path(unresolved_path); ++ } ++ return Qnil; ++ } ++} ++ ++if (origenc != rb_enc_get(resolved)) { ++ if (!rb_enc_str_asciionly_p(resolved)) { ++ resolved = rb_str_conv_enc(resolved, NULL, origenc); ++ } ++ rb_enc_associate(resolved, origenc); ++} ++ ++if(rb_enc_str_coderange(resolved) == ENC_CODERANGE_BROKEN) { ++ rb_enc_associate(resolved, rb_filesystem_encoding()); ++ if(rb_enc_str_coderange(resolved) == ENC_CODERANGE_BROKEN) { ++ rb_enc_associate(resolved, rb_ascii8bit_encoding()); ++ } ++} ++ ++rb_obj_taint(resolved); ++RB_GC_GUARD(unresolved_path); + return resolved; + } + Index: 2.5/Makefile === RCS file: /cvs/ports/lang/ruby/2.5/Makefile,v retrieving