randomized pids removal

Discuss usability issues, general maintenance, and general support issues for a grsecurity-enabled system.

randomized pids removal

Postby noix » Sun Jan 14, 2007 4:02 pm

Hi,

I think I've been using this feature and I'm not sure why it had to be removed ("provides no useful additional security"?)

What I need it for is: kill all processes belonging to a specified user reliably (being root).
I thought the best way was:
Code: Select all
fork+setuid+kill(-1, SIGKILL)

However, without randomized pids, it seems a bit racy - the user can kill or ptrace the killing process between setuid and kill.

Could anyone please tell me a better way? It's entirely possible there's much better way already.
(Or I might be too paranoid - such race condition could be hard to win even without randomized pids...)
noix
 
Posts: 8
Joined: Thu Oct 26, 2006 7:02 pm

Postby spender » Mon Jan 15, 2007 2:01 pm

There's a utility called 'slay' that can be used to do this.

-Brad
spender
 
Posts: 2185
Joined: Wed Feb 20, 2002 8:00 pm

Postby noix » Mon Jan 15, 2007 2:21 pm

spender wrote:There's a utility called 'slay' that can be used to do this.

Thanks, I know this utility...

I want to write it on my own, as a part of a bigger program. I need such functionality as "kill all processes owned by x", "stop (SIGSTOP) all processes owned by y" etc.

Slay does exactly what I thought was the cleanest way - fork+setuid+kill(-1). This is a snippet from "slay" source code:
Code: Select all
su -m $slayee -c "kill $SIGNAL -1"

I might be wrong, but I believe what slay does is unreliable: the user whose processes I'm going to kill can kill or ptrace the killing process after it's uid has been changed and before it did kill().
I also believe it would become much more reliable with grsec's random pids! (Then, the attacker would probably use inotify/dnotify on /proc, and it would be considerably harder for him to win the race).

Another way to do this would be to iterate though /proc and send the signal to each process ("skill" utility does that)... But I don't think it's that good either, because I wouldn't SIGSTOP/SIGCONT all processes at once.
(Also, I don't think one iteration over /proc is enough to be sure I stopped/killed all processes of that user... I'm worried about that user forking() in a clever way while I'm iterating)

What do you think about it?
noix
 
Posts: 8
Joined: Thu Oct 26, 2006 7:02 pm

Re: randomized pids removal

Postby Alexei.Sheplyakov » Wed Jan 17, 2007 3:52 am

noix wrote:Hi,

I think I've been using this feature and I'm not sure why it had
to be removed ("provides no useful additional security"?)

What I need it for is: kill all processes belonging to a specified
user reliably
(being root).

I thought the best way was:
Code: Select all
fork+setuid+kill(-1, SIGKILL)

However, without randomized pids, it seems a bit racy - the user can kill or ptrace
the killing process between setuid and kill.
Could anyone please tell me a better way?
It's entirely possible there's much better way already.


What about making your binary SGID (to some dummy group)? Thus process
in question can not be ptraced (unless user has CAP_SYS_PTRACE).
I'm not really sure if it is a better way, though.

noix wrote:(Or I might be too paranoid - such race condition could be hard to
win even without randomized pids...)


Just because you're paranoid doesn't mean they aren't after you (TM)
Alexei.Sheplyakov
 
Posts: 53
Joined: Sun Feb 19, 2006 11:48 am

Re: randomized pids removal

Postby noix » Wed Jan 17, 2007 4:36 am

Alexei.Sheplyakov wrote:What about making your binary SGID (to some dummy group)? Thus process in question can not be ptraced (unless user has CAP_SYS_PTRACE).
I'm not really sure if it is a better way, though.

Good idea, I haven't thought of it...
But it only solves the problem of ptrace() - you still can send signals to a SGID binary process!

Summing up:
- (I'm root)
- I want to send a signal to all processes owned by user x at once
- do it in 100% reliable way
I just can't find a way to do it in Linux :/ And random pids did help... but it's possible I'm just looking in the wrong place from the beginning.
noix
 
Posts: 8
Joined: Thu Oct 26, 2006 7:02 pm

Postby fixinko » Wed Jan 17, 2007 12:48 pm

something like this?
Code: Select all
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <pwd.h>
#include <signal.h>
#include <errno.h>

int main(int argc, char * argv[]) {
    struct passwd *pwd;
   
    if (argc != 2) {
        printf("Usage: %s [user]\n",argv[0]);
        exit(-1);
    }
    if((pwd = getpwnam(argv[1])) == NULL) {
        printf("User '%s' doesn't exist.\n", argv[1]);
        exit(-1);
    }
    if(setuid(pwd->pw_uid)) {
        perror("setuid():");
        exit(-1);
    }
    if(kill(-1,9)) {
        perror("kill():");
        exit(-1);
    }
    return 0;
}
fixinko
 
Posts: 6
Joined: Wed Apr 19, 2006 11:58 am

Postby noix » Wed Jan 17, 2007 1:42 pm

fixinko wrote:something like this?
Code: Select all
snip

Yes, exactly what I was talking about...
Does anything prevent user argv[1] from killing (or SIGSTOPing or ptracing) your program's process between
Code: Select all
    if(setuid(pwd->pw_uid)) {
        perror("setuid():");
        exit(-1);
    }

and
Code: Select all
    if(kill(-1,9)) {
        perror("kill():");
        exit(-1);
    }

?
noix
 
Posts: 8
Joined: Thu Oct 26, 2006 7:02 pm

Postby Alexei.Sheplyakov » Thu Jan 18, 2007 5:33 am

noix wrote:Does anything prevent user argv[1] from killing (or SIGSTOPing
or ptracing) your program's process between
Code: Select all
    if(setuid(pwd->pw_uid)) {
        perror("setuid():");
        exit(-1);
    }

and
Code: Select all
    if(kill(-1,9)) {
        perror("kill():");
        exit(-1);
    }

?


But if we use
Code: Select all
if (setresuid(pwd->pw_uid, pwd->pw_uid, uid_t(0)) {
   perror(setresuid():");
   exit(-1);
}


instead, that is, set real and effective UIDs to one of the user in
question and saved UID to root, then the kernel protects the "killer"
process from being ptraced and signaled by the target user
(kernel/signal.c:573):

Code: Select all
/*
 * Bad permissions for sending the signal
 */
static int check_kill_permission(int sig, struct siginfo *info,
             struct task_struct *t)
{
   int error = -EINVAL;
   if (!valid_signal(sig))
      return error;
   error = -EPERM;
   if ((info == SEND_SIG_NOINFO || (!is_si_special(info) && SI_FROMUSER(info)))
       && ((((sig != SIGCONT) ||
      (current->signal->session != t->signal->session))
       && (current->euid ^ t->suid) && (current->euid ^ t->uid)
       && (current->uid ^ t->suid) && (current->uid ^ t->uid)
       && !capable(CAP_KILL)) || gr_handle_signal(t, sig)))
      return error;

   error = security_task_kill(t, info, sig);
   if (!error) {
      audit_signal_info(sig, t); /* Let audit system see the signal */
      gr_log_signal(sig, t);
   }
   return error;
}


The "killer" process itself is only allowed to kill processes owned by
target user only (from the code above one can see that current->suid == 0
does not give any additional privileges). (BTW, this makes unnecessary SGID
trick I've mentioned earlier).

On the other hand, one could try to write grsecurity policy which disables
ptrace'ing and signalling the "killer" binary.
Alexei.Sheplyakov
 
Posts: 53
Joined: Sun Feb 19, 2006 11:48 am

Postby noix » Fri Jan 19, 2007 12:10 pm

Alexei.Sheplyakov wrote:But if we use
Code: Select all
if (setresuid(pwd->pw_uid, pwd->pw_uid, uid_t(0)) {
   perror(setresuid():");
   exit(-1);
}


instead, that is, set real and effective UIDs to one of the user in
question and saved UID to root, then the kernel protects the "killer"
process from being ptraced and signaled by the target user

It seems like looking in the right place, but it still doesn't work.
I tried the following program:
Code: Select all
//(snip - includes)
    int main(int argc, char * argv[]) {
    struct passwd *pwd;

    if (argc != 2) {
        printf("Usage: %s [user]\n",argv[0]);
        exit(-1);
    }
    if((pwd = getpwnam(argv[1])) == NULL) {
        printf("User '%s' doesn't exist.\n", argv[1]);
        exit(-1);
    }
    if(setresuid(pwd->pw_uid, pwd->pw_uid, uid_t(0))) {
        perror("setuid()");
        exit(-1);
    }
    sleep(10);
    return 0; }

...and when I run it with "./prog noix" i still could kill it with noix user while it sleeps.

Alexei.Sheplyakov wrote:(kernel/signal.c:573):

Code: Select all
/*
 * Bad permissions for sending the signal
 */
static int check_kill_permission(int sig, struct siginfo *info,
             struct task_struct *t)
{
   int error = -EINVAL;
   if (!valid_signal(sig))
      return error;
   error = -EPERM;
   if ((info == SEND_SIG_NOINFO || (!is_si_special(info) && SI_FROMUSER(info)))
       && ((((sig != SIGCONT) ||
      (current->signal->session != t->signal->session))
       && (current->euid ^ t->suid) && (current->euid ^ t->uid)
       && (current->uid ^ t->suid) && (current->uid ^ t->uid)
       && !capable(CAP_KILL)) || gr_handle_signal(t, sig)))
      return error;

   error = security_task_kill(t, info, sig);
   if (!error) {
      audit_signal_info(sig, t); /* Let audit system see the signal */
      gr_log_signal(sig, t);
   }
   return error;
}

It looks like just the place to look for a possible answer, but I'm not done crunching those xor-s yet ;)
Alexei.Sheplyakov wrote:The "killer" process itself is only allowed to kill processes owned by
target user only (from the code above one can see that current->suid == 0
does not give any additional privileges). (BTW, this makes unnecessary SGID
trick I've mentioned earlier).

I'm not sure what you mean...
Saved uid does give additional privileges, as (according to man page) non-root processes can still seteuid() to it's saved or real uid.
Alexei.Sheplyakov wrote:On the other hand, one could try to write grsecurity policy which disables
ptrace'ing and signalling the "killer" binary.

Yes, I also thought about it.
But I think it's a little clumsy this way - maybe because I haven't used RBACLs yet...
noix
 
Posts: 8
Joined: Thu Oct 26, 2006 7:02 pm

Postby noix » Sat Jan 20, 2007 2:15 pm

noix wrote:
Alexei.Sheplyakov wrote:(kernel/signal.c:573):

Code: Select all
/*
 * Bad permissions for sending the signal
 */
static int check_kill_permission(int sig, struct siginfo *info,
             struct task_struct *t)
{
   int error = -EINVAL;
   if (!valid_signal(sig))
      return error;
   error = -EPERM;
   if ((info == SEND_SIG_NOINFO || (!is_si_special(info) && SI_FROMUSER(info)))
       && ((((sig != SIGCONT) ||
      (current->signal->session != t->signal->session))
       && (current->euid ^ t->suid) && (current->euid ^ t->uid)
       && (current->uid ^ t->suid) && (current->uid ^ t->uid)
       && !capable(CAP_KILL)) || gr_handle_signal(t, sig)))
      return error;

   error = security_task_kill(t, info, sig);
   if (!error) {
      audit_signal_info(sig, t); /* Let audit system see the signal */
      gr_log_signal(sig, t);
   }
   return error;
}

It looks like just the place to look for a possible answer, but I'm not done crunching those xor-s yet ;)

I think I know how it works now...
It is just as the man page says:
the real or effective user ID of the sending process must equal the real or saved set-user-ID of the target process

(it's even more restrictive for ptrace())
The best solution I found so far is: change the effective uid to uid of the "killed" user (say, "noix") and the saved and real uid to some other user owning no processes - say, "nobody".
It's almost perfect: this process can kill processes of "noix", but it can't be killed by a process of "noix". It should also work with passing -1 as pid.

There is only one problem: I have to be careful not to run many "killer" processes at a time, because the killer process also kills all processes with real or saved uid "nobody".

Thanks for pointing me in the right direction, Alexei :)
noix
 
Posts: 8
Joined: Thu Oct 26, 2006 7:02 pm

Postby PaX Team » Tue Jan 30, 2007 6:43 pm

mikeeusa wrote:If you removed it could you put it back in please? Or recurit additional team members if your removing because it's too much work?
random pids were removed because pid namespace handling in 2.6 made it very hard to support (not worth the effort), but good luck in finding someone else to do it for you.
PaX Team
 
Posts: 2310
Joined: Mon Mar 18, 2002 4:35 pm


Return to grsecurity support

cron