I'm having trouble getting setuid to work as hoped (chmod 3755).
I'm trying to implement, using C language, my own version of psgrep (or pgrep) for a very specific need. In order to accomplish this, I need to get the value of the link at /proc/[pid]/exe for every listed [pid]
To demonstrate the issue, I made this tiny program: (proof.c)
#define _GNU_SOURCE
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
char procFilename[] = "/proc/1/exe" ;
char pointsTo[80] ;
int rc ;
memset(pointsTo, 0x00, sizeof(pointsTo)) ;
rc = readlink(procFilename, pointsTo, sizeof(pointsTo)) ;
if ( rc < 0 ) {
perror("Trying to read /proc/1/exe link") ;
}
else {
printf("%s points to %s.\n", procFilename, pointsTo) ;
}
}
In the same folder, I exeucte:
gcc -o proof proof.c
sudo chown root:root proof
sudo chmod 3755 proof
Now, here are the execution results:
> ./proof
Trying to read /proc/1/exe link: Permission denied
> sudo ./proof
/proc/1/exe points to /usr/lib/systemd/systemd.
> ls -l proof
-rwxr-sr-t 1 root root 16888 Feb 16 14:06 proof
As I understand it, the chmod and chown combination should have gotten me past the permission error, but obviously I am missing something.
When I searched stackoverflow for "sticky-bit problems" there were no good matches (though a surprising number of people expect setuid to work with scripts). One problem about 2.6 kernel didn't seem to fit either. Anyway, what might I be missing?
Related
I created a VERY simple script:
//#escalate.c - a setuid utility so that we can call shutdown
//# and other things safely without needing root access. We
//# do need to:
//# gcc escalate.c -o escalate.out
//# sudo chown root:root escalate.out
//# sudo chmod 4755 escalate.out
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
int main()
{
int status;
status = setuid( 0 ); // you can set it at run time also
system("date > /tmp/date.fil");
return errno;
}
On Raspian it generates the file in /tmp, owned by the root and returns 0 as expected.
On Ubuntu 22 it created the file owned by ME and the return status is 1. What am I missing about setuid(0); ?
I tried creating, modifying the permissions and ownership etc. On Raspian it works like a charm, on Ubuntu it does not.
==================
OK - solved it myself. On Ubuntu I was running with an encrypted home and so it was mounted with nosuid set.
the problem was that the file system was mounted nosuid
I wrote the following code expecting to spawn a /bin/sh from another user.
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char **argv, char **envp)
{
setresgid(getegid(), getegid(), getegid());
setresuid(geteuid(), geteuid(), geteuid());
execve("/bin/sh", argv, envp);
return 0;
}
I then changed the owner to match with my target user and changed permissions (too much, I know)
chown usertarget:globalgroup ./shell
chmod 777 ./shell
chmod +s ./shell
ls -lah shell
Everything is fine according to me. However, It keeps opening a shell as my current user, not the target one.
I already tried to hardcode the userid of my target user and a few other things (setuid function, ...) but nothing seems to work...
Anyone has an idea or anything that could help me investigate this problem ?
EDIT #1
baseuser#machine:/tmp/tata$ ls -lah shell2
-rwsrwsrwx 1 targetuser globalgroup 7.2K Aug 18 18:21 shell2
baseuser#machine:/tmp/tata$ id
uid=1507(baseuser) gid=1314(globalgroup) groups=1314(globalgroup),100(users)
baseuser#machine:/tmp/tata$ ls -lah shell2
-rwsrwsrwx 1 targetuser globalgroup 7.2K Aug 18 18:21 shell2
baseuser#machine:/tmp/tata$ ./shell2
====== WELCOME USER ======
baseuser#machine:/tmp/tata$ id -a
uid=1507(baseuser) gid=1314(globalgroup) groups=1314(globalgroup),100(users)
baseuser#machine:/tmp/tata$
Well in facts, the parition was mounted with nosuid option. This can be checked through mount command
I would like to use syscalls to get the id of the current user. I tried it like this:
#include <unistd.h>
#include <sys/syscall.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
int uid = syscall(SYS_getuid);
printf("%d\n", uid);
return 0;
}
I executed it with root, but it prints -1 instead of 0.
If I replace it with int uid = syscall(SYS_getuid);, then it correctly returns 0. What do I wrong? How to get the current user id using syscall?
I run it on i686/ubuntu docker image, because I have to create 32bit executables.
Minimal reproducible example:
Dockerfile
FROM i686/ubuntu
RUN apt-get update
RUN apt-get install --assume-yes --no-install-recommends --quiet \
gcc libc6-dev
RUN apt-get clean all
main.c
#include <unistd.h>
#include <sys/syscall.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(int argc, char *argv[])
{
int uid = syscall(SYS_getuid);//getuid();//
if(uid == -1)
printf("Error: %s\n", strerror(errno));
printf("%d\n", uid);
return 0;
}
Run ( On x64 Windows10 ):
docker build --platform linux/386 -t my-gcc .
docker run --platform linux/386 --rm -v ${pwd}:/usr/local/src/:rw my-gcc gcc -m32 -xc /usr/local/src/main.c -o /usr/local/src/main
docker run --platform linux/386 --rm -v ${pwd}:/usr/local/src/:rw my-gcc /usr/local/src/main
The result is:
Error: Function not implemented
-1
Per getuid(2):
The original Linux getuid() and geteuid() system calls supported only 16-bit user IDs. Subsequently, Linux 2.4 added getuid32() and geteuid32(), supporting 32-bit IDs. The glibc getuid() and geteuid() wrapper functions transparently deal with the variations across kernel versions.
Apparently you are running your program on a kernel that has the old getuid system call compiled out, and only getuid32 is available on x86-32. If you run fgrep CONFIG_UID16 "/boot/config-$(uname -r)", you will be able to see if your running kernel supports the 16-bit syscall. If this command prints anything other than CONFIG_UID16=y, it means the old system call is unavailable.
If you invoke SYS_getuid32 instead, it should work fine. Note that SYS_getuid32 may fail to be available on other architectures.
what I did is:
1. sudo rm -rf /root/.debug/
2. compile program with -g -O2 -fno-omit-frame-pointer
3. run the program and get the pid
4. sudo perf record -F 2000 -a -s -g -p $pid sleep 15
5. sudo perf report
then I get a tiny part of "unknown", like
- 2.50% 0.00% postgres [unknown] [k] 0000000000000000
- 0
1.12% _int_malloc ▒
0.79% _IO_vsnprintf
looks this is due to libc 'malloc' call. then I write a program on the same machine to test it.
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
int main(int argc, char *argv[])
{
while(1) {
printf("perf record -g -p %d -- sleep 5; perf report\n", getpid());
sleep(1);
void *p = malloc(10);
memset(p, 0, 10);
free(p);
}
return 0;
}
then I did the same thing as above, there is no "unknown" section.
how to explain/fix this?
The [unknown] block in the perf report output refers to the name of the dynamic shared object (DSO). perf report could not resolve the DSO path and hence it prints [unknown]. Per the latest kernel source code tree (which is 5.3.9 at the time of writing), you can see this here.
It is important to know that the determination of the DSO symbols happen with the help of the sampled event address. The function thread__resolve is responsible for doing exactly that. In older kernels, the thread__resolve method had another name - perf_event__preprocess_sample_addr.
Given the snapshot of your output, it looks like the address of the event that was sampled during perf record, is 0. This means that the address could not be resolved at all. It is an address in the kernel space (looking at the symbol [k] 0000000000000000) and perf in your case, could not resolve it.
The comments highlight setting perf_event_paranoid to a suitable value so that you can probe both kernel and user-space events successfully. Setting perf_event_paranoid to a value that allows you to correctly probe events in the kernel space should "be a step" towards correctly resolving the address.
I am writing a program using execv() that compiles and runs another program. I've written up a simple C program named helloWorld.c that when executed outputs, "Hello world," and a second file named testExec.c that is supposed to compile and run helloWorld.c. I've been looking around everywhere to find a way to do this, but I haven't found any answers. The code in testExec.c is:
#include <stdio.h>
#include <unistd.h>
int main(){
char *args[] = {"./hellWorld.c", "./a.out", NULL};
execv("usr/bin/cc", args);
return 0;
}
testExec.c compiles with no errors. However, when I run it I get an error that says, "fatal error: -fuse-linker-plugin, but liblto_plugin.so not found. compilation terminated." Which I think means helloWorld.c is being compiled but when it comes time to run helloWorld.c this error is thrown. I thought maybe that was because I had a.out and helloWorld.c prefaced with './'. I removed './' from both, then either one individually, and still no luck.
I also did 'sudo apt-get install build-essential' along with 'sudo apt-get install gcc'. I wasn't sure if that would resolve the issue but I really wasn't sure what else to try. Anyway, any help would be appreciated!
You're missing the leading slash when calling cc.
Also, the first argument in the argument list is the name of the executable. The actual arguments come after that. You're also not using -o to specify the name of the output file.
#include <stdio.h>
#include <unistd.h>
int main(){
char *args[] = {"cc", "-o", "./a.out", "./hellWorld.c", NULL};
execv("/usr/bin/cc", args);
return 0;
}
EDIT:
The above only compiles. If you want to compile and run, you can do this:
#include <stdio.h>
#include <unistd.h>
int main(){
system("cc -o ./a.out ./hellWorld.c");
execl("./a.out", "a.out", NULL);
return 0;
}
Although this is probably best done as a shell script:
#!/bin/sh
cc -o ./a.out ./hellWorld.c
./a.out