I'm trying something very simple with setuid followed by getuid in a C program that is not working unless the user is the owner of the c program. I am setting the permissions bit using chmod 7777 in addition to thesetuid. Here is the C program...
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
int x;
printf("Setting uid to 1111\n");
x=setuid(1111);
printf("setuid returned... %d\n",x);
printf("Testing state of uid using getuid()\n");
printf("uid: %d\n",getuid());
return 0;
}
Given that 1111 is the uid of the owner of the C program (returned by id -u).
after gcc, chmod 7777 on the binary produced.
Testing as the owner works fine...
% suid
Setting uid to 1111
setuid returned... 0
Testing state of uid using getuid()
uid: 1112
and to check the permissions on the binary...
ls -l suid
-rwsrwsrwx 1 katman design 6852 Mar 1 17:56 suid
Then I become another user (same group BTW) and run the "suid" executable again...
% suid
Setting uid to 1111
setuid returned... 0
Testing state of uid using getuid()
uid: 57849
Why isn't setuid working given that the owner granted permission to do this?
Also...
Q: If I can get this to work, I have heard that one can do a system() call to run a cshell script from a C program like this and the shell script should run as the owner of the C program. The uid is "passed down" to children. True or False?
Q: If the cshell script runs a perl script which in turn runs system calls, etc... will all these offspring shells run as the owner of the parent C binary that started started the whole cascade? IOW, is the uid "inherited" by children?
Related
I am trying to debug a C program using GDB. But when I try to run it from inside gdb I get the following error :
note: the FATAL error line is user defined
gdb-peda$ run
Starting program: /home/masterdungeon/HTAOEBookPrograms/0x200/0x280/0x287/GameOfChance
**************** WELCOME to the GAME OF CHANCE *****************
This game will essentially tell you how lucky you are today ;)
---- New player ----
Please enter your name : user_gdb
[!!!] Fatal Error in register_user() while opening DATAFILE
: Permission denied
[Inferior 1 (process 10636) exited with code 0377]
Warning: not running
gdb-peda$
This program is actually a command line game named "GameOfChance" (from the book HTAOE). Whenever a user runs the program, the program first checks its UserID to see whether the user is already registered as a player in the DATAFILE. If there is no entry of that UID in the DATAFILE(i.e player not registered already), then the program allows to create a new player and accept a username, thus registering as a player with that UID and accepted username. But I think GDB does not have a UID since there is no entry of gdb in /etc/passwd. How do I make the program run while debugging and register GDB as a new player? Is it even possible ?
The code looks like this :
12 #define DATAFILE "/var/gameofchance.data" // File to store user data
46 int main(){
//lines of code
53 uid = getuid(); // get current user_id i.e player_id
54 player_exists = get_player_data(uid); // returns -1 if player does not exist
55 //otherwise returns 0 and puts all player data into struct player
56
57 if(player_exists == -1) {
58 register_player(uid);
59 }
//lines of code
148 return 0;
149 } //end main()
314 void register_player(int uid){
//lines of code
327 fd = open(DATAFILE, O_WRONLY|O_CREAT|O_APPEND, S_IRUSR|S_IWUSR);
329
330 if(fd==-1){
331 fatal(" in register_user() while opening DATAFILE\n");
332 }
//lines of code
344 } //end register_player
the permissions for DATAFILE are :
-rw------- 1 root masterdungeon 240 Apr 19 13:54 gameofchance.data
the permissions for executable game GameOfChance are :
-rwsrwxr-x 1 root root 29064 Jan 4 19:45 GameOfChance
Another thing I couldn't understand is when I set a breakpoint at line 54 and check for value of uid I get 1000 as UID of GDB.
Breakpoint 16, main () at gameofchance.c:54
54 player_exists = get_player_data(uid); // returns -1 if player does not exist
gdb-peda$ x/wd &uid
0x7ffd4ed4aee8: 1000
How's it possible that GDB has userid of 1000 ? as there is no entry of gdb in /etc/passwd. 1000 is userid of masterdungeon.
Okay so it works when gdb is run using sudo gdb. But why do I have to run it as root to get it run nicely in GDB ?
Otherwise in BASH the program runs successfully as user masterdungeon. Only in GDB it require to be run as root
Does GDB have a userid?
Yes. Every process that runs, including GDB processes, has both an effective UID and a real UID. Often these are the same. But you seem to have a misunderstanding. These do not describe the process itself. Rather they describe the user on whose behalf the process is running.
How's it possible that GDB has userid of 1000 ? as there is no entry of gdb in /etc/passwd. 1000 is userid of masterdungeon.
Because you're running gdb as user "masterdungeon", or as another user with the same UID number.
Okay so it works when gdb is run using sudo gdb. But why do I have to run it as root to get it run nicely in GDB ?
Your data file is accessible only to root:
-rw------- 1 root masterdungeon 240 Apr 19 13:54 gameofchance.data
. When run directly, the program accommodates that by being root-owned and having its SUID bit set:
-rwsrwxr-x 1 root root 29064 Jan 4 19:45 GameOfChance
(note the "s" in the first triad of permission bits). That causes the program, when run directly, to run with the effective UID of root, even though root did not actually launch it. This is one of the cases where the effective and real UIDs differ. It is also a very poor use case for SUID, because SUID root programs present an existential security risk to the host system, and that risk is not justified for a game.
The risk would be much worse if the SUID bit were honored when the program is running under control of a debugger. A debugger can make arbitrary changes to program data and even binary code while the program is running, and that would present an easy vector for privilege escalation if SUID were honored in such contexts. Accordingly, the SUID bit on an executable has no effect when the program is run in a debugger. (See also Can gdb debug suid root programs?)
Thus, if you debug the program as a user other than root, it will not be able to open the data file, but if you use sudo to run the debugger then you obtain the needed privelege to access the data file through sudo, and the fact that the SUID bit on the executable is not honored is irrelevant.
The best way to debug the program is in its build environment, before installation, such that it is owned by you and does not need (or have) its SUID bit set. This may require some manipulation of where or how it looks for its data file, which should also be owned by you.
As for how the program is installed, you have a tension between priorities:
Programs available for all users to run should be owned by root and writable only by root, to make it difficult for other users to modify them or substitute different program for them, both of which could lead to data breach and (further) privilege escalation.
You apparently require that users running the game program be able to write to a shared data file. It's unclear what this file contains, but a shared high score list might be an example.
But you do not (presumably) want to allow users to manipulate the data file arbitrarily, under their own authority, lest they cheat in some way, or worse.
The easiest approach would be to give each user their own, unshared data file, created at need by the program within the user's home directory, and accessible to that user. Then you don't need to mess with SUID / SGID, nor do you need to have any concern about users interfering with each other. Sure, they may be able to cheat, but it will affect only them. And you will be able to debug the program with GDB.
If it is essential that the data file be both shared among program users and writable (via the program) to all of them, then a better approach than making the program SUID-root would be to make it SGID-some_group_not_root, and make the data file writable by that group. Better still, avoid the SGID bit, and just require users to be members of the chosen group in order to use the program. Do note that SGID is not honored when debugging, either.
This is a repost after being referred to "Calling a script from a setuid root C program - script does not run as root" for a solution
My problem is different in that I do not want to run the C program (and c-shell script called inside) as root. Rather, I want to run as the owner of the c program file.
Also, I tried with "setuid(0)" as this was the solution in the referenced post. This is reflected in the edited code below. Same results.
Also, I opened permissions up all the way this time with "chmod 7775" (just in case it was a permissions problem)
Here's the original note with edits to reflect the change to "setuid(0)"
I'm having a problem implementing an simple example that would demonstrate how setuid can be made to run a binary with the uid of the file owner of the binary. What I would eventually like to do is run a c-shell script using this technique.
I read that this will not work for shell scripts but also heard of a work-around using a C program to run a system() call that'll run the c-shell script ( e.g. system("source my.csh") ). I wrote a C program that attempts this, plus simply reports the current uid. This is what I tried...
From my current shell, I did a "su katman" to become the user of the binary I want to run as user katman.
I created a C program try.c. ...
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
setuid(0);
printf("In try.c ... sourcing katwhoami.csh...\n");
system( "source /home/me/su_experiments/katwhoami.csh");
printf("In try.c ... using straight system call...\n");
system("whoami");
return 0;
}
I "setuid(0)" as recommended by a reference to a different note. But earlier, I tried setting it to the uid of the C program's owner as obtained with "id -u".
The katwhoami.csh shell script is simply...
date
echo "In katwhoami.csh, I am "`whoami`
echo "In katwhoami.csh, I am "$USER
exit
Then I compiled the C program and set the bit...
% gcc -o try try.c
% chmod 7775 try
% ls -l try
-rwsrwsr-x 1 katman design 6784 Mar 1 11:59 try
And then I test it...
% try
In try.c ... sourcing katwhoami.csh...
Thu Mar 1 12:28:28 EST 2018
In katwhoami.csh, I am ktadmin
In katwhoami.csh, I am ktadmin
In try.c ... using straight system call...
ktadmin
...which is what I expected.
I exit to get back to the shell I started from, hoping that if I run try there, it'll tell me I'm "katman"....
% exit
% whoami
daveg
% try
In try.c ... sourcing katwhoami.csh...
Thu Mar 1 12:30:04 EST 2018
In katwhoami.csh, I am daveg
In katwhoami.csh, I am daveg
In try.c ... using straight system call...
daveg
... which is not what I was hoping for :-(
As you can probably tell, I'm new at using this.
Any help would be appreciated !
Update....
I tried a new sample program, a.c...
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
FILE *fh;
setuid(1234);
system("whoami");
fh=fopen("test.file","w");
fprintf(fh,"Here I am\n");
fclose(fh);
return 0;
}
I compiled and set the bit
gcc -o a a.c
chmod 4775 a
Then I exited the "su" and ran as a user that is NOT the owner of the binary. Same result as before with regard to reported uid (the current uid), BUT, the owner of the file that the C program created ion this version was the owner of the C program !!! So that worked.
Is there something fishy about the "whoami" command? system() call ?
If I do a "system("source some.csh") does it create a new shell which assumes the original uid (not the owner of the C binary) ? Something like that ?
I really need the new uid to "stick" as far as child processes.
I have this C file:
#include <stdio.h>
#include <unistd.h>
int main(void)
{
printf("%s\n", getlogin());
printf("%i\n", getuid());
}
I compile it, set the UID and GID both to root and set the setuid bit, so that it looks like this:
-rwsrwsr-x 1 root root 8735 Apr 8 19:51 a.out
However when I call $ ./a.out I still get:
user
1000
What am I doing wrong?
The real user ID is still the user that called the program, but the effective user ID is root. In a setuid program, they are not the same.
To get the effective user ID, call geteuid(). You can also use cuserid() to get the name associated with the effective user ID.
Your program has got only the permission to change its uid. To actually switch to root you have to call setuid(0) in it.
Please have a look here
I've the following simple code to check my getuid function:
uidActual=getuid();
printf ("User id is [%d]\n", uidActual);
error=setuid(197623);
printf("[%d]",error);
uidActual=getuid();
printf ("\n User id is [%d]\n", uidActual);
But it always returns a -1 as error, so the uid doesn't change.
The 197623 in setuid seems right as I've, apart from other things, the following in my mkpasswd command:
user1: 197609:197609[...]
user2: 197623:197121[...]
Where 197609 and 197623 must be the id for the user as in fact I start the application with user 1 and I obtain its id properly displaying at the beginning and the end: "User id is 197609".
I've set all permissions for everyone on the created executable and I've even run the executable as a root in cygwin with cygstart --action=runas ./a.exe and it still doesn't work.
Funny thing is that the setgid (for changing group) function works perfectly with setgid(197121), even without special permissions or running. So I'm out of ideas of why this function always returns an error.
Any idea on what is wrong on my code that could be causing the problem?
Thanks for your attention.
the posted code must be run by a user with appropriate privileges. Perhaps by:
sudo ./a.exe
here is an excerpt from the (linux) man page for setuid()
"EPERM The user is not privileged (Linux: does not have the CAP_SETUID capability) and uid does not match the real UID or saved set-user-ID of the calling process."
I have a C program that calls setgid() with the group id of the group "agrp", and it is saying "Operation not permitted" when I try to run it.
The program has the following ls -la listing:
-r-xr-s--x 1 root agrp 7508 Nov 18 18:48 setgidprogram
What I want, is setgidprogram to be able to access a file that has the owner otheruser and the group agrp, and permissions set to u+rw,g+rw (User and group read/writeable.)
What am I doing wrong? Does setgidprogram HAVE to have the setuid bit set also? (When I tried it, it worked.)
I am running Fedora 19, and I have SELinux disabled.
EDIT
Here is some example code:
wrap.c:
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <grp.h>
int main(void)
{
struct group *grp = getgrnam("agrp");
printf("%d\n",grp->gr_gid);
if(setgid(grp->gr_gid) != 0)
{
printf("%s.\n", strerror(errno));
return 1;
}
execl("/tmp/whoami_script.sh", NULL);
printf("%s.\n", strerror(errno));
return 0;
}
/tmp/whoami_script.sh:
#!/usr/bin/bash
id
$ ls -la /tmp/whoami_script.sh wrap
-r-xr-xr-x 1 root agrp 19 Nov 18 19:53 /tmp/whoami_script.sh
$ ./wrap
1234
uid=1000(auser) gid=1000(auser) groups=1000(auser),0(root),10(wheel)
---x--s--x 1 root agrp 7500 Nov 18 19:55 wrap
Is this enough information now?
The original version of the question showed 6550 permission on the file.
If you're not either user root or in group agrp, you need to be able to use the public execute permissions on the program — which are missing. Since it is a binary, you don't need read permission. To fix it:
# chmod o+x setgidprogram
(The # denotes 'as root or via sudo', or equivalent mechanisms.) As it stands, only people who already have the relevant privileges can use the program.
If the program is installed SGID agrp, there is no need for the program to try to do setgid(agrp_gid) internally. The effective GID will be the GID belonging to agrp and the program will be able to access files as any other member of agrp could.
That said, normally you can do a no-op successfully. For example, this code works fine:
#include <stdio.h>
#include <unistd.h>
#include "stderr.h"
int main(int argc, char **argv)
{
err_setarg0(argv[argc-argc]);
gid_t gid = getegid();
if (setgid(gid) != 0)
err_syserr("Failed to setgid(%d)\n", (int)gid);
puts("OK");
return 0;
}
(You just have to accept that the err_*() function do error reporting; the argc-argc trick avoids a warning/error from the compiler about otherwise unused argument argc.)
If you make the program SUID root, then the SGID property doesn't matter much; the program will run with EUID root and that means it can do (almost) anything. If it is SUID root, you should probably be resetting the EUID to the real UID:
setuid(getuid());
before invoking the other program. Otherwise, you're invoking the other program as root, which is likely to be dangerous.
Dissecting POSIX
In his answer, BenjiWiebe states:
The problem was I was only setting my effective GID, not my real GID. Therefore, when I exec'd, the child process was started with the EGID set to the RGID. So, in my code, I used setregid() which worked fine.
Yuck; which system does that? Linux trying to be protective? It is not the way things worked classically on Unix, that's for sure. However, the POSIX standard seems to have wriggle room in the verbiage (for execvp()):
If the ST_NOSUID bit is set for the file system containing the new process image file, then the effective user ID, effective group ID, saved set-user-ID, and saved set-group-ID are unchanged in the new process image. Otherwise, if the set-user-ID mode bit of the new process image file is set, the effective user ID of the new process image shall be set to the user ID of the new process image file. Similarly, if the set-group-ID mode bit of the new process image file is set, the effective group ID of the new process image shall be set to the group ID of the new process image file. The real user ID, real group ID, and supplementary group IDs of the new process image shall remain the same as those of the calling process image. The effective user ID and effective group ID of the new process image shall be saved (as the saved set-user-ID and the saved set-group-ID) for use by setuid().
If I'm parsing that right, then we have a number of scenarios:
ST_NOSUID is set.
ST_NOSUID is not set, but SUID or SGID bit is set on the executable.
ST_NOSUID is not set, but SUID or SGID biy is not set on the executable.
In case 1, it is fairly clearly stated that the EUID and EGID of the exec'd process are the same as in the original process (and if the EUID and RUID are different in the original process, they will be different in the child).
In case 2, if the SUID bit is set on the executable, the EUID will be set to the SUID. Likewise if the SGID bit is set on the executable, the EGID will be set to the SGID. It is not specified what happens if the SUID bit is set, the SGID bit is not set, and the original process has different values for EGID and RGID; nor, conversely, is it specified what happens if the SGID bit is set, the SUID bit is not set, and the original process has different values for EUID and RUID.
Case 3, where neither the SUID nor SGID bit is set on the executable, also seems to be unspecified behaviour.
Classically on Unix systems, the EUID and RUID could be different, and the difference would be inherited across multiple (fork() and) exec() operations if the executable does not override the EUID or EGID with its own SUID or SGID bits. However, it is not clear that the POSIX standard mandates or prohibits this; it seems to be unspecified behaviour. The rationale section provides no guidance on the intentions.
If my reading is correct, then I find it amusing that the ST_NOSUID bit means that if a program is launched by a process that is running SUID, then the program on the 'no SUID' file system will be run with different real and effective UID (RUID and EUID), which seems counter-intuitive. It doesn't matter what the SUID and SGID bits on the executable are set to (so the bits on the executable are ignored), but the inherited values of EUID and RUID are maintained.
This code finally worked:
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <grp.h>
int main(void)
{
gid_t g = getegid();
if(setregid(g, g) != 0)
{
printf("Error setting GID: %s.\n", strerror(errno));
}
execl("/tmp/whoami_script.sh", "/tmp/whoami_script.sh", NULL);
printf("Error: %s.\n", strerror(errno));
return 0;
}
The problem was I was only setting my effective GID, not my real GID. Therefore, when I exec'd, the child process was started with the EGID set to the RGID. So, in my code, I used setregid which worked fine.