$SAFE=0 for setuid?

Hi

From most documentation I see that $SAFE is automatically set to 1 if
you run ruby as root.

However, this causes pcap that I’m using not to function (it throws a
SecurityError).

I need $SAFE=0 for pcap to function, however I also need to run it with
root privileges for it to function, but as soon as setuid is used, $SAFE
changes to 1.

Is there a way to have the safety level be overridden to 0 with setuid
active? Otherwise I don’t see how a library like pcap can be used.

Thanks

From most documentation I see that $SAFE is automatically set to 1 if
you run ruby as root.

Not entirely true, it sets $SAFE to 1 if you run it with setuid, but
just running as root $SAFE will still be 0.

I need $SAFE=0 for pcap to function, however I also need to run it with
root privileges for it to function, but as soon as setuid is used, $SAFE
changes to 1.

Is there a way to have the safety level be overridden to 0 with setuid
active? Otherwise I don’t see how a library like pcap can be used.

If you need to run it setuid, I found some C code that claims to be
able to do this (with lots and lots of warnings that it’s a bad idea
and could damage your system) here:

And yeah, it really is a bad idea to use that code… terribly
insecure. Just execute it as root with sudo or su unless you REALLY
need setuid.

-Jonathan N.

On Sun, Apr 4, 2010 at 7:43 PM, Rick A. [email protected]
wrote:

changes to 1.

Is there a way to have the safety level be overridden to 0 with setuid
active? Otherwise I don’t see how a library like pcap can be used.

ruby -e ‘p [Process.uid, $SAFE]’
[0, 0]

I don’t see what’s hindering you.

On 04/04/2010 06:32 PM, Rick A. wrote:

http://www.sveinbjorn.org/platypus_tutorial#33)

Not sure what I can do here :confused:

Write a wrapper script with setuid. You can even do such unsafe things
as

#!/bin/sh -f
“$@”

Why is it that sudo won’t raise the safe level but setuid does? Surely
they equally escalate privileges?

Setuid can be detected by the Ruby interpreter because it is a property
of the script executed. sudo is just a process that changes the
environment in which the Ruby interpreter is started. This is
significantly more difficult to detect since sudo is gone once the
interpreter runs:

robert@fussel:~$ sudo pstree -u $$
bash(robert)???pstree(root)
robert@fussel:~$

Kind regards

robert

Robert K. wrote:

Write a wrapper script with setuid. You can even do such unsafe things
as

#!/bin/sh -f
“$@”

Yeah I did try this already as:
#!/usr/bin/env sh
ruby script.rb

but no dice it still gives me safe level 1 on start :frowning: I’m guessing the
child processes inherit the flag?

Setuid can be detected by the Ruby interpreter because it is a property
of the script executed. sudo is just a process that changes the
environment in which the Ruby interpreter is started. This is
significantly more difficult to detect since sudo is gone once the
interpreter runs

Ah ok thanks, that makes sense :slight_smile: However I was asking more about the
reasons for the design / philosophy rather than why it practically
doesn’t work with sudo. According to what you say then, sudo works not
because it’s by design (due to it being safer for some reason) but
because there’s no way to catch a sudo? But this means that by design,
there should be no way that libraries like pcap should be able to
function (since they need to be run as root and need SAFE=0), and that
the only reason they do work is because Ruby can’t detect when they do
run with escalated privs in the case of sudo?

This means that by design the only way to use the pcap library is to log
in as root without escalating the current user’s privileges? This
however isn’t even possible in many linux distros like Ubuntu, as well
as in Mac OS X, since the root account is disabled and users should only
do root stuff through sudo. If that’s the case, it seems like it’s all a
bit wrong to me, or am I understanding something wrong?

Rick A. wrote:

This means that by design the only way to use the pcap library is to log
in as root without escalating the current user’s privileges? This
however isn’t even possible in many linux distros like Ubuntu

sudo works fine in Ubuntu. Even if you run from the live CD, you can do
“sudo bash” to get a root shell.

However, the reason for $SAFE=1 is that setuid scripts are very
dangerous. If you want the full reasons, read
http://www.faqs.org/faqs/unix-faq/faq/part4/section-7.html
This talks about shell scripts, but the same issues apply to setuid ruby
scripts.

Escalating to root properly, using su or sudo or even a setuid C wrapper
program, makes some of the awkward problems disappear.

Of course, you still have the dangers of running a program as root which
has to be very careful about validating all its inputs etc, but that’s a
separate and more obvious issue.

Brian C. wrote:

sudo works fine in Ubuntu. Even if you run from the live CD, you can do
“sudo bash” to get a root shell.

That’s what I’m saying. You need to escalate the current user’s privs
using sudo. You can’t have root privs without escalation (ie. log in as
root) in Ubuntu or OS X since the root account is disabled as it uses
the sudo paradigm.

However, unlike Robert, what you’re saying is that it’s specifically
setuid that’s problematic, not the privilege escalation itself, and it’s
the setuid issues that are the reason to have safe level set to 1 rather
than the privilege escalation. That makes sense.

Regardless, there isn’t then a good workaround for what I need? :confused:

Jonathan N. wrote:

Not entirely true, it sets $SAFE to 1 if you run it with setuid, but
just running as root $SAFE will still be 0.

Ok thanks. Yes, running with sudo will have $SAFE set to 0, however, I’m
currently wrapping the script in an app bundle using Platypus.

This doesn’t allow user input into a terminal so I cannot use sudo (see:
http://www.sveinbjorn.org/platypus_tutorial#33)

However it does allow the entire script to be run as admin using the
Apple Security Framework. I’m unsure about the exact details of the
framework but it appears to start the process with setuid.

With this entry point then, it doesn’t seem to matter what I do (whether
I start Ruby directly or I start ruby through sh), $SAFE is always 1
when the script starts.

Starting ruby with -T0 doesn’t seem to do anything.

Not sure what I can do here :confused:

Why is it that sudo won’t raise the safe level but setuid does? Surely
they equally escalate privileges?

Did you remove the setuid flag from the Ruby script before you tested
this approach?

Well there’s nothing in the actual ruby script that does setuid. I was
assuming this is a flag on the process that then gets inherited from the
parent process? Is there a way to start the the ruby script then that
explicitly tells it to not have setuid? But does this then also mean it
doesn’t get root privs (which it needs)?

2010/4/6 Rick A. [email protected]:

Robert K. wrote:

Write a wrapper script with setuid. You can even do such unsafe things
as

#!/bin/sh -f
“$@”

Btw, that should have read

#!/bin/sh -f
exec “$@”

It’s more efficient.

Yeah I did try this already as:
#!/usr/bin/env sh
ruby script.rb

Rather state the path to ruby explicitly. Otherwise you’ll introduce
another security hole.

but no dice it still gives me safe level 1 on start :frowning: I’m guessing the
child processes inherit the flag?

Did you remove the setuid flag from the Ruby script before you tested
this approach?

Cheers

robert

BTW, here’s exactly what happens (ruby.c):

static void
init_ids()
{
uid = (int)getuid();
euid = (int)geteuid();
gid = (int)getgid();
egid = (int)getegid();
#ifdef VMS
uid |= gid << 16;
euid |= egid << 16;
#endif
if (uid && (euid != uid || egid != gid)) {
rb_set_safe_level(1);
}
}

That is: if the real uid is not root, and either the effective uid
different to the real uid or the effective gid is different to the real
gid, then set $SAFE to 1.

But notice that $SAFE level 1 only means “Ruby disallows the use of
tainted data by potentially dangerous operations”. So maybe what you
should do is go with the flow, and carefully validate and untaint all
data which comes from external sources. Maybe ruby pcap will work if you
do this.

See Programming Ruby: The Pragmatic Programmer's Guide for
more info.

On 4/6/10, Rick A. [email protected] wrote:

That’s what I’m saying. You need to escalate the current user’s privs
using sudo. You can’t have root privs without escalation (ie. log in as
root) in Ubuntu or OS X since the root account is disabled as it uses
the sudo paradigm.

Although (logging in to) the root account is disabled by_default on
Ubuntu, you can certainly enable it. I believe the same is true of the
mac, tho it’s been a while since I really used a mac. Just because
they try to shove sudo down your throat doesn’t mean you have to
swallow it.

Regardless, there isn’t then a good workaround for what I need? :confused:

There almost certainly is one for Mac, but I’m not a Mac user.

sudo doesn’t require any user input if you configure it correctly (i.e.
with suitable use of NOPASSWD flag in the sudoers file).

Many Ubuntu GUI apps prompt for a password when they need to escalate to
root privileges (e.g. update-manager). I don’t know what that framework
they use as I’ve never had to use it myself.

Anyway, the way that ruby checks setuid is to test for real uid !=
effective uid (grep for “forbid_setid” in the ruby source). So a simple
C program which is setuid root and which sets real id to effective id is
all you need.

$ cat myrunner.c
// gcc -Wall -o myrunner myrunner.c
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
if (setgid(getegid()) < 0) { perror(“setgid”); return 1; }
if (setuid(geteuid()) < 0) { perror(“setuid”); return 1; }
execl("/usr/bin/ruby",“ruby”,"/home/candlerb/myscript.rb",NULL);
return 1;
}

$ gcc -Wall -o myrunner myrunner.c
$ sudo chown 0:0 myrunner
$ sudo chmod 4755 myrunner
$ cat myscript.rb
p $SAFE
$ ./myrunner
0

Note that I have intentionally hard-coded the name of the script to run,
and the path to the ruby interpreter, into the wrapper. You don’t want
people being able to run arbitrary code as root. A better wrapper would
also reset the environment (man execle)

HTH,

Brian.

Brian C. wrote:

sudo works fine in Ubuntu. Even if you run from the live CD, you can do
“sudo bash” to get a root shell.

or “sudo -i”:

-i [command]
The -i (simulate initial login) option runs the shell
specified in the passwd(5) entry of the target user as a
login shell. This means that login-specific resource
files
such as .profile or .login will be read by the shell. If
a
command is specified, it is passed to the shell for
execution. Otherwise, an interactive shell is executed.
sudo attempts to change to that user’s home directory
before running the shell. It also initializes the
environment, leaving DISPLAY and TERM unchanged, setting
HOME, SHELL, USER, LOGNAME, and PATH, as well as the
contents of /etc/environment on Linux and AIX systems.
All
other environment variables are removed.

When setting up an ubuntu system, I always used to set up a root
password (just “sudo passwd root” IIRC), until I found out about the -i
option. Now I just use that instead.

Thanks for all the helpful responses guys :slight_smile:

Brian C. wrote:

Note that I have intentionally hard-coded the name of the script to run,
and the path to the ruby interpreter, into the wrapper.

I can’t hard-code the path this way. By design, a Mac app bundle should
be able to live anywhere, not just under a particular path, so I can’t
do this. Will just have to live with the security implication then. Of
course, in the case of the example you provide, there’s nothing stopping
someone / something from modifying / replacing
/home/candlerb/myscript.rb anyways, right?

So maybe what you
should do is go with the flow, and carefully validate and untaint all
data which comes from external sources.

You are totally right! :slight_smile: Because the SecurityError had come from inside
of open_live in the pcap library, and gave no reference to anything of
mine, I had assumed something in that library was getting some tainted
input. I had forgotten the fact that I had modified my program to get
the network interface based on the currently active interface (which
changes dynamically in OS X). This was then part of what got passed to
open_live and I overlooked that. Dope! Untainting the network interface
string fixed that.

Solved! Thanks for the help :slight_smile:

Rick A. wrote:

I can’t hard-code the path this way. By design, a Mac app bundle should
be able to live anywhere, not just under a particular path, so I can’t
do this. Will just have to live with the security implication then. Of
course, in the case of the example you provide, there’s nothing stopping
someone / something from modifying / replacing
/home/candlerb/myscript.rb anyways, right?

Well, obviously you don’t give setuid permissions to a file and then
allow anyone to edit it :slight_smile: It would have to be stored in a trusted
location. Ditto the path to the ruby interpreter itself.

The C program could validate that the script lives in a trusted location
(that only an administrator could modify). Or calculate an SHA1. Or
include your actual script source inline, and link against libruby to
run it (again, in the assumption that only a trusted user would be able
to replace libruby)

Untainting the network interface
string fixed that.

Solved! Thanks for the help :slight_smile:

Excellent news!

On 4/7/10, Brian C. [email protected] wrote:

Well, obviously you don’t give setuid permissions to a file and then
allow anyone to edit it :slight_smile: It would have to be stored in a trusted
location. Ditto the path to the ruby interpreter itself.

The C program could validate that the script lives in a trusted location
(that only an administrator could modify). Or calculate an SHA1. Or

I believe that using a sha1 in this way would create a TOCTOU race
condition; in other words, it’s not secure. Don’t do that.