Setuid wrapper

hate to reply to my own message - but here is the program i’m using, posted to
clr for posterity. no security flames please! :wink:

----CUT----
/*
FILE: _ruby.c

  • this program is a wrapper on ruby which will run ruby as another user
    (setuid ruby).

  • it MUST be named ‘USERNAME_ruby’, for example ‘nobody_ruby’, and must also
    be a root owned setuid binary

  • compile with

    gcc _ruby.c -o USERNAME_ruby
    sudo su
    chown root USERNAME_ruby
    chgrp root USERNAME_ruby
    chmod 6777 USERNAME_ruby

    for example

    gcc _ruby.c -o nobody_ruby
    sudo su
    chown root nobody_ruby
    chgrp root nobody_ruby
    chmod 6777 nobody_ruby

    obviously the user this ruby would execute as is user ‘nobody’

  • this program is known to work with

    • Linux 2.4.18-27.8.0 i686
    • ruby 1.6.7 (2002-03-19) [i386-linux]
  • this program could be VERY dangerous to your system’s health: use wisely
    */

#define RUBY “ruby”

#include <stdlib.h>
#include <stdio.h>
#include <pwd.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

int
die (const char * const msg, int err, const char * const file, int lineno)
{
fprintf (stderr, “%s @ %s:%d\n”, msg ? msg : strerror (err), file, lineno);
exit (err);
}
#define DIE() (die(strerror(errno),errno,FILE,LINE))

#define unless(exp) if(!(exp))

int
main (argc, argv, env)
int argc;
char **argv;
char **env;
{
int n;
char *ruby;
char *cp;
char *name;
uid_t pw_uid;
gid_t pw_gid;
struct passwd *entry;

/* find basename of executing program */
for (cp = *argv + strlen (*argv) - 1; cp >= *argv && *cp != ‘/’; --cp);
(*cp == ‘/’) && cp++;

/* make a copy because we will munge it later */
unless (name = strdup (cp))
{
DIE ();
}

/* search for XXX component of XXX_ruby */
for (cp = name; *cp && *cp != ‘'; cp++);
unless (*cp && cp != name && *cp == '
’ && (strcmp (cp + 1, “ruby”) == 0))
{
die (“THIS PROGRAM MUST BE NAMED ${USER}_ruby”, EXIT_FAILURE, FILE, LINE);
}

/* we take everything before the ‘_’ as the username */
*cp = 0;
unless (entry = getpwnam (name))
{
die (name, errno, FILE, LINE);
}

pw_uid = entry->pw_uid;
pw_gid = entry->pw_gid;

free (name);

setregid (pw_gid, pw_gid);
/* setgid (pw_gid); */
setfsgid (pw_gid);

setreuid (pw_uid, pw_uid);
/* setuid (pw_uid); */
setfsuid (pw_uid);

(ruby = getenv (“RUBY”)) || (ruby = RUBY);
execvp (ruby, argv);
return (EXIT_FAILURE);
}
----CUT----

-a

···

On Thu, 28 Aug 2003, ahoward wrote:

i wrote a little setuid C wrapper which essentially does

execvp (ruby, argv);

and was suprised that ruby was still aware that it was being run setuid and
set $SAFE to 1. is there an easy away around this? perhpas embedding the
interpreter?

====================================

Ara Howard
NOAA Forecast Systems Laboratory
Information and Technology Services
Data Systems Group
R/FST 325 Broadway
Boulder, CO 80305-3328
Email: ara.t.howard@noaa.gov
Phone: 303-497-7238
Fax: 303-497-7259
~ > ruby -e ‘p(%.\x2d\x29…intern)’
====================================

Hi,

setregid (pw_gid, pw_gid);
/* setgid (pw_gid); */
setfsgid (pw_gid);

setreuid (pw_uid, pw_uid);
/* setuid (pw_uid); */
setfsuid (pw_uid);

These set both of real and effective IDs, so ruby cannot know
if it’s invoked as setuid.

(ruby = getenv (“RUBY”)) || (ruby = RUBY);

In general, using environment variables in setuid program
should be more careful.

And this code does no check for the given script itself. I
can’t help warning it is very dangerous.

···

At Fri, 29 Aug 2003 08:56:42 +0900, ahoward wrote:


Nobu Nakada