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/Makefile        3 Apr 2019 17:25:25 -0000       1.16
+++ 2.4/Makefile        26 Jun 2019 19:45:33 -0000
@@ -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 -0000
+++ 2.4/patches/patch-file_c    26 Jun 2019 20:11:48 -0000
@@ -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 <limits.h>
++#include <stdlib.h>
++
+ 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, &st) < 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 revision 1.8
diff -u -p -r1.8 Makefile
--- 2.5/Makefile        15 Mar 2019 16:45:36 -0000      1.8
+++ 2.5/Makefile        26 Jun 2019 19:20:45 -0000
@@ -3,6 +3,7 @@
 VERSION =              2.5.5
 SHARED_LIBS =          ruby25  0.0
 NEXTVER =              2.6
+REVISION-main =                0
 
 PSEUDO_FLAVORS=                no_ri_docs bootstrap
 # Do not build the RI docs on slow arches
Index: 2.5/patches/patch-configure
===================================================================
RCS file: /cvs/ports/lang/ruby/2.5/patches/patch-configure,v
retrieving revision 1.2
diff -u -p -r1.2 patch-configure
--- 2.5/patches/patch-configure 31 Mar 2018 21:12:45 -0000      1.2
+++ 2.5/patches/patch-configure 26 Jun 2019 19:21:00 -0000
@@ -14,7 +14,7 @@ in earlier ruby versions).
 Index: configure
 --- configure.orig
 +++ configure
-@@ -19989,14 +19989,14 @@ fi
+@@ -19991,14 +19991,14 @@ fi
    if test $rb_cv_page_size_log != no; then :
  
      cat >>confdefs.h <<_ACEOF
@@ -31,7 +31,7 @@ Index: configure
  _ACEOF
  
  
-@@ -26268,7 +26268,7 @@ fi
+@@ -26270,7 +26270,7 @@ fi
    openbsd*|mirbsd*) :
  
        SOLIBS='$(LIBS)'
@@ -40,7 +40,7 @@ Index: configure
         ;; #(
    solaris*) :
  
-@@ -27743,7 +27743,7 @@ _ACEOF
+@@ -27745,7 +27745,7 @@ _ACEOF
  
  else
  
Index: 2.5/patches/patch-ext_openssl_extconf_rb
===================================================================
RCS file: /cvs/ports/lang/ruby/2.5/patches/patch-ext_openssl_extconf_rb,v
retrieving revision 1.1
diff -u -p -r1.1 patch-ext_openssl_extconf_rb
--- 2.5/patches/patch-ext_openssl_extconf_rb    23 Feb 2018 09:54:25 -0000      
1.1
+++ 2.5/patches/patch-ext_openssl_extconf_rb    26 Jun 2019 19:21:00 -0000
@@ -3,7 +3,7 @@ $OpenBSD: patch-ext_openssl_extconf_rb,v
 Index: ext/openssl/extconf.rb
 --- ext/openssl/extconf.rb.orig
 +++ ext/openssl/extconf.rb
-@@ -134,6 +134,7 @@ have_func("HMAC_CTX_free")
+@@ -144,6 +144,7 @@ have_func("HMAC_CTX_free")
  OpenSSL.check_func("RAND_pseudo_bytes", "openssl/rand.h") # deprecated
  have_func("X509_STORE_get_ex_data")
  have_func("X509_STORE_set_ex_data")
Index: 2.5/patches/patch-file_c
===================================================================
RCS file: 2.5/patches/patch-file_c
diff -N 2.5/patches/patch-file_c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ 2.5/patches/patch-file_c    26 Jun 2019 19:21:00 -0000
@@ -0,0 +1,112 @@
+$OpenBSD$
+
+Backport use of realpath(3) for File.realpath to allow unveil(2) to work.
+
+Index: file.c
+--- file.c.orig
++++ file.c
+@@ -131,6 +131,9 @@ int flock(int, int);
+ #  define UTIME_EINVAL
+ #endif
+ 
++#include <limits.h>
++#include <stdlib.h>
++
+ VALUE rb_cFile;
+ VALUE rb_mFileTest;
+ VALUE rb_cStat;
+@@ -4057,7 +4060,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;
+@@ -4151,6 +4154,76 @@ rb_check_realpath_internal(VALUE basedir, VALUE path, 
+     return resolved;
+ }
+ 
++static VALUE rb_file_join(VALUE ary);
++
++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));
++    }
++    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,
++         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, &st) < 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;
++}
++
+ VALUE
+ rb_realpath_internal(VALUE basedir, VALUE path, int strict)
+ {
+@@ -4572,8 +4645,6 @@ rb_file_s_split(VALUE klass, VALUE path)
+     FilePathStringValue(path);                /* get rid of converting twice 
*/
+     return rb_assoc_new(rb_file_dirname(path), rb_file_s_basename(1,&path));
+ }
+-
+-static VALUE rb_file_join(VALUE ary);
+ 
+ static VALUE
+ file_inspect_join(VALUE ary, VALUE arg, int recur)
Index: 2.6/Makefile
===================================================================
RCS file: /cvs/ports/lang/ruby/2.6/Makefile,v
retrieving revision 1.5
diff -u -p -r1.5 Makefile
--- 2.6/Makefile        27 May 2019 21:42:01 -0000      1.5
+++ 2.6/Makefile        26 Jun 2019 18:02:41 -0000
@@ -5,7 +5,7 @@ DISTNAME =              ruby-${VERSION}
 SHARED_LIBS =          ruby26  0.0
 NEXTVER =              2.7
 
-REVISION-main =                0
+REVISION-main =                1
 MASTER_SITES0 =                https://github.com/ruby/ruby/commit/
 PATCHFILES =           1ef39d8d099f145222b9352423af16a2bab6e05b.patch:0
 PATCH_DIST_STRIP =     -p1
Index: 2.6/patches/patch-file_c
===================================================================
RCS file: 2.6/patches/patch-file_c
diff -N 2.6/patches/patch-file_c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ 2.6/patches/patch-file_c    26 Jun 2019 18:02:13 -0000
@@ -0,0 +1,112 @@
+$OpenBSD$
+
+Backport use of realpath(3) for File.realpath to allow unveil(2) to work.
+
+Index: file.c
+--- file.c.orig
++++ file.c
+@@ -132,6 +132,9 @@ int flock(int, int);
+ #  define UTIME_EINVAL
+ #endif
+ 
++#include <limits.h>
++#include <stdlib.h>
++
+ VALUE rb_cFile;
+ VALUE rb_mFileTest;
+ VALUE rb_cStat;
+@@ -4064,7 +4067,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;
+@@ -4158,6 +4161,76 @@ rb_check_realpath_internal(VALUE basedir, VALUE path, 
+     return resolved;
+ }
+ 
++static VALUE rb_file_join(VALUE ary);
++
++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));
++    }
++    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,
++         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, &st) < 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;
++}
++
+ VALUE
+ rb_realpath_internal(VALUE basedir, VALUE path, int strict)
+ {
+@@ -4579,8 +4652,6 @@ rb_file_s_split(VALUE klass, VALUE path)
+     FilePathStringValue(path);                /* get rid of converting twice 
*/
+     return rb_assoc_new(rb_file_dirname(path), rb_file_s_basename(1,&path));
+ }
+-
+-static VALUE rb_file_join(VALUE ary);
+ 
+ static VALUE
+ file_inspect_join(VALUE ary, VALUE arg, int recur)

Reply via email to