Setuid/setgid ruby scripts

Hey folks,

Yesterday I needed to make a program setgid, but as you probably know,
you can’t (at least, not under linux) just set the setuid or setgid
bits, as it won’t be honored for #! scripts.

Anyway, my first solution was to embed a ruby script in an executable
and have the executable run it. That worked, but was a little bit
annoying to write, and lost the advantage of being easily modifiable
(easily==no recompile, etc).

So, I made a “better” version that was just:
#include <unistd.h>
int main(int argc, char *argv[]) {
const char *script = “/absolute/path/to/ruby/script.rb”
execv(script, argv);
}
And then setuid the result.

Anyway, I was just wondering if anyone has ever needed to make a ruby
script setuid or setgid before–and if so, how did you go about doing
it?

Any comments about this solution in general? Seems like it’s possible
that there could be some sort of potential exploit through environment
poisoning or something, but I can’t think of anything off-hand. (In
this example, it was setgid to a specific single-task group, so I’m not
too worried about it; but as a general case, it would be more important
to think about…)

(For the curious, the actual application is a nethack restore-saved-game
script, for those of us who sometimes like to cheat at nethack (mostly
my wife, but sometimes me too!). Code attached. :wink:

nethack_restore.cpp (143 Bytes)

nethack_restore-script.rb (596 Bytes)

···


Wesley J. Landaker - wjl@icecavern.net
OpenPGP FP: C99E DF40 54F6 B625 FD48 B509 A3DE 8D79 541F F830

Hi,

So, I made a “better” version that was just:
#include <unistd.h>
int main(int argc, char *argv[]) {
const char *script = "/absolute/path/to/ruby/script.rb"
execv(script, argv);
}
And then setuid the result.

A perl script creates such wrappers can be found in Camel book.

Anyway, I was just wondering if anyone has ever needed to make a ruby
script setuid or setgid before–and if so, how did you go about doing
it?

I had, but suid/sgid security issue has not been discussed
about enough yet.

Index: Makefile.in

···

At Tue, 3 Jun 2003 10:29:16 +0900, Wesley J Landaker wrote:

RCS file: /cvs/ruby/src/ruby/Makefile.in,v
retrieving revision 1.44
diff -u -2 -p -r1.44 Makefile.in
Makefile.in 21 May 2003 11:51:55 -0000 1.44
+++ Makefile.in 3 Jun 2003 02:27:18 -0000
@@ -36,7 +36,9 @@ MAINLIBS = @MAINLIBS@

RUBY_INSTALL_NAME=@RUBY_INSTALL_NAME@
+SRUBY_INSTALL_NAME=@SRUBY_INSTALL_NAME@
RUBY_SO_NAME=@RUBY_SO_NAME@
EXEEXT = @EXEEXT@
PROGRAM=$(RUBY_INSTALL_NAME)$(EXEEXT)
+SPROGRAM=$(SRUBY_INSTALL_NAME)$(EXEEXT)
RUBY = $(RUBY_INSTALL_NAME)
MINIRUBY = @MINIRUBY@
@@ -119,4 +121,8 @@ $(PROGRAM): $(LIBRUBY) $(MAINOBJ) $(
$(PURIFY) $(CC) $(LDFLAGS) $(XLDFLAGS) $(MAINLIBS) $(MAINOBJ) $(EXTOBJS) $(LIBRUBYARG) $(LIBS) -o $@

+$(SPROGRAM): $(LIBRUBY) $(MAINOBJ) $(EXTOBJS) suid.@OBJEXT@

  •   @rm -f $@
    
  •   $(PURIFY) $(CC) $(LDFLAGS) $(XLDFLAGS) $(OUTFLAG)$@ $(MAINLIBS) $(MAINOBJ) $(EXTOBJS) suid.@OBJEXT@ $(LIBRUBYARG) $(LIBS)
    

$(LIBRUBY_A): $(OBJS) dmyext.@OBJEXT@
@AR@ rcu $@ $(OBJS) dmyext.@OBJEXT@
@@ -360,2 +366,3 @@ variable.@OBJEXT@: variable.c ruby.h con
version.@OBJEXT@: version.c ruby.h config.h defines.h intern.h missing.h
version.h
+suid.@OBJEXT@: suid.c missing.h ruby.h rubyio.h
Index: configure.in

RCS file: /cvs/ruby/src/ruby/configure.in,v
retrieving revision 1.173
diff -u -2 -p -r1.173 configure.in
configure.in 1 Jun 2003 12:58:56 -0000 1.173
+++ configure.in 1 Jun 2003 16:16:56 -0000
@@ -379,5 +379,5 @@ AC_CHECK_FUNCS(fmod killpg wait4 waitpid
getpgrp setpgrp getpgid setpgid getgroups getpriority getrlimit
dlopen sigprocmask sigaction _setjmp setsid telldir seekdir fchmod\

  •     mktime timegm cosh sinh tanh)
    
  •     mktime timegm cosh sinh tanh group_member)
    

AC_STRUCT_TIMEZONE
AC_CACHE_CHECK(for struct tm.tm_gmtoff, rb_cv_member_struct_tm_tm_gmtoff,
@@ -993,4 +993,25 @@ esac
AC_SUBST(setup)

+case $target_os in
+cygwin*|mswin*|mingw*|djgpp*|os2_emx*|human*);;
+*)

  • AC_CACHE_CHECK([for setuid/setgid supported],
  • rb_cv_setugid,
  • [AC_TRY_CPP([
    +#include <sys/stat.h>
    +#ifndef S_ISUID
    +#ifndef S_ISGID
    +#error setuid/setgid not supported
    +#endif
    +#endif
  • ],
  • rb_cv_setugid=yes,
  • rb_cv_setugid=no)])
  • if test “$rb_cv_setugid” = yes; then
  • AC_LIBOBJ([suiddmy])
  • fi
  • ;;
    +esac

if test “$prefix” = NONE; then
prefix=$ac_default_prefix
@@ -1213,4 +1234,5 @@ AC_SUBST(LIBRUBY_LDSHARED)
AC_SUBST(LIBRUBY_DLDFLAGS)
AC_SUBST(RUBY_INSTALL_NAME)
+AC_SUBST(SRUBY_INSTALL_NAME)
AC_SUBST(rubyw_install_name)
AC_SUBST(RUBYW_INSTALL_NAME)
@@ -1245,4 +1267,5 @@ test “$program_suffix” != NONE &&

RUBY_INSTALL_NAME="${ri_prefix}ruby${ri_suffix}"
+SRUBY_INSTALL_NAME="${ri_prefix}suidruby${ri_suffix}"
case “$target_os” in
cygwin*|mingw*)
Index: file.c

RCS file: /cvs/ruby/src/ruby/file.c,v
retrieving revision 1.146
diff -u -2 -p -r1.146 file.c
— file.c 19 May 2003 05:41:07 -0000 1.146
+++ file.c 19 May 2003 06:06:56 -0000
@@ -425,5 +425,6 @@ rb_file_lstat(obj)
}

-static int
+#ifndef HAVE_GROUP_MEMBER
+int
group_member(gid)
GETGROUPS_T gid;
@@ -454,4 +455,5 @@ group_member(gid)
return Qfalse;
}
+#endif

#ifndef S_IXUGO
Index: ruby.c

RCS file: /cvs/ruby/src/ruby/ruby.c,v
retrieving revision 1.77
diff -u -2 -p -r1.77 ruby.c
— ruby.c 22 Apr 2003 11:58:08 -0000 1.77
+++ ruby.c 22 Apr 2003 15:40:17 -0000
@@ -25,4 +25,5 @@
#include <stdio.h>
#include <sys/types.h>
+#include <sys/stat.h>
#include <ctype.h>

@@ -793,4 +794,8 @@ load_file(fname, script)
rb_io_binmode(f);
}
+#elif defined S_ISUID || defined S_ISGID

  • if (script) {
  •   ruby_check_loadable(f);
    
  • }
    #endif
    }
    Index: suid.c
    ===================================================================
    RCS file: suid.c
    diff -N suid.c
    — /dev/null 1 Jan 1970 00:00:00 -0000
    +++ suid.c 3 Oct 2001 01:55:06 -0000
    @@ -0,0 +1,150 @@
    +/**********************************************************************
  • suid.c -
  • $Author$
  • $Date$
  • created at: Wed Aug 29 13:45:03 JST 2001
  • This file is covered under the Ruby’s license (see the file
  • COPYING).

+**********************************************************************/
+
+#include “ruby.h”
+#include “rubyio.h”
+#include <errno.h>
+#include <sys/stat.h>
+
+#if !defined S_ISUID && !defined S_ISGID
+#error “setuid/setgid not supported”
+#endif
+
+void
+ruby_check_loadable(f)

  • VALUE f;
    +{
  • OpenFile *fptr;
  • struct stat st;
  • int setid = 0;
  • int uid, euid, gid, egid;
    +#ifdef HAVE_SETRESUID
  • int suid;
    +#endif
    +#ifdef HAVE_SETRESGID
  • int sgid;
    +#endif
  • rb_check_type(f, T_FILE);
  • GetOpenFile(f, fptr);
  • if (fstat(fileno(fptr->f), &st) == -1) {
  • rb_sys_fail(fptr->path);
  • }

+#ifdef HAVE_SETRESUID

  • getresuid(&uid, &euid, &suid);
    +#else
  • uid = (int)getuid();
  • euid = (int)geteuid();
    +#endif
    +#ifdef HAVE_SETRESGID
  • getresgid(&gid, &egid, &sgid);
    +#else
  • gid = (int)getgid();
  • egid = (int)getegid();
    +#endif
  • if (uid != 0) {
  • if ((
    +#if defined S_IRUSR || defined S_IXUSR
  •   (st.st_uid == uid) ?
    
  •   !(st.st_mode & (0
    

+#ifdef S_IRUSR

  •   	    |S_IRUSR
    

+#endif
+#ifdef S_IXUSR

  •   	    |S_IXUSR
    

+#endif

  •   )) :
    

+#endif
+#if defined S_IRGRP || defined S_IXGRP

  •   group_member(st.st_gid) ?
    
  •   !(st.st_mode & (0
    

+#ifdef S_IRGRP

  •   	    |S_IRGRP
    

+#endif
+#ifdef S_IXGRP

  •   	    |S_IXGRP
    

+#endif

  •   )) :
    

+#endif
+#if defined S_IROTH || defined S_IXOTH

  •   !(st.st_mode & (0
    

+#ifdef S_IROTH

  •   	    |S_IROTH
    

+#endif
+#ifdef S_IXOTH

  •   	    |S_IXOTH
    

+#endif

  •   ))
    

+#else

  •   0
    

+#endif

  •   )
    

+#if defined S_ISUID && defined S_IWGRP

  •   || !(~st.st_mode & (S_ISUID|S_IWGRP))
    

+#endif
+#if defined S_ISUID && defined S_IWOTH

  •   || !(~st.st_mode & (S_ISUID|S_IWOTH))
    

+#endif
+#if defined S_ISGID && defined S_IWOTH

  •   || !(~st.st_mode & (S_ISGID|S_IWOTH))
    

+#endif

  •   ) {
    
  •   errno = EACCES;
    
  •   rb_load_fail(fptr->path);
    
  • }
  • }

+#ifdef S_ISGID

  • if (st.st_mode & S_ISGID) {
    +# if defined HAVE_SETEGID
  • setegid(st.st_gid);
    +# elif defined HAVE_SETREGID
  • setregid(-1, st.st_gid);
    +# else
  • setgid(st.st_gid);
    +# endif
  • setid = 1;
  • }
  • else
    +#endif
    +#if defined HAVE_SETRESGID && defined HAVE_SETEGID
  • setegid(sgid);
    +#else
  • setgid(gid);
    +#endif

+#ifdef S_ISUID

  • if (st.st_mode & S_ISUID) {
    +# if defined HAVE_SETEUID
  • seteuid(st.st_uid);
    +# elif defined HAVE_SETREUID
  • setreuid(-1, st.st_uid);
    +# else
  • setuid(st.st_uid);
    +# endif
  • setid = 1;
  • }
  • else
    +#endif
    +#if defined HAVE_SETRESUID && defined HAVE_SETEUID
  • seteuid(suid);
    +#else
  • setuid(uid);
    +#endif
  • if (!setid) {
  • rb_loaderror(“not setuid/setgid – %s”, fptr->path);
  • }
    +}
    Index: suiddmy.c
    ===================================================================
    RCS file: suiddmy.c
    diff -N suiddmy.c
    — /dev/null 1 Jan 1970 00:00:00 -0000
    +++ suiddmy.c 6 Sep 2001 10:25:58 -0000
    @@ -0,0 +1,4 @@
    +void
    +ruby_check_loadable()
    +{
    +}


Nobu Nakada

Hi,

···

In message “Re: setuid/setgid ruby scripts” on 03/06/03, nobu.nokada@softhome.net nobu.nokada@softhome.net writes:

Anyway, I was just wondering if anyone has ever needed to make a ruby
script setuid or setgid before–and if so, how did you go about doing
it?

I had, but suid/sgid security issue has not been discussed
about enough yet.

A wrapper is a friend of you.

My grandpa once told me that never try suidruby, before he died 30
years ago. At that time, I didn’t understand what he meant.

						matz.

Thank you, for that totally ridiculous image at 8:00am… that made my morning!

And before he died, he gasped, “Do not believe IBM, token ring will fail,
invest
in 3com…”

“I will avenge you…”

···

At 16:26 03/06/2003 +0900, Matz wrote:

My grandpa once told me that never try suidruby, before he died 30
years ago. At that time, I didn’t understand what he meant.

                                                    matz.