books/apitue/sample-code/03/setuid.c

230 lines
6.0 KiB
C
Raw Permalink Normal View History

2024-01-20 14:39:54 +00:00
/* This file is part of the sample code and exercises
* used by the class "Advanced Programming in the UNIX
* Environment" taught by Jan Schaumann
* <jschauma@netmeister.org> at Stevens Institute of
* Technology.
*
* This file is in the public domain.
*
* You don't have to, but if you feel like
* acknowledging where you got this code, you may
* reference me by name, email address, or point
* people to the course website:
* https://stevens.netmeister.org/631/
*/
/* This trivial program illustrates the use of
* setuid(2) and seteuid(2). After compiling,
* chmod(1) and chown(1) as needed to access a
* privileged resource (e.g. /etc/sudoers).
*
* Note: this example ignores group IDs. See
* https://is.gd/l6pu5K for details on the
* order of dropping all privileges correctly.
*
* Example invocation:
* cc -Wall setuid.c
* sudo chown root a.out
* sudo chmod 4755 a.out
* ./a.out /etc/sudoers
*
* Note: after chowning, try to recompile. Does
* your compiler overwrite a.out? Why/why not?
*
* Note: setuid is not restricted to setuid-0. Try
* with another user!
*
* mkdir -m 0700 /tmp/daemon
* touch /tmp/daemon/somefile
* chmod a+r /tmp/daemon/somefile
* sudo chown daemon /tmp/daemon
* sudo chown daemon a.out
* sudo chmod 4755 a.out
* ls -l a.out
* ls -l /tmp/daemon
* ls -ld /tmp/daemon
* ./a.out /tmp/daemon/somefile
* ./a.out /etc/sudoers
*/
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define UNPRIVILEGED_UID 1
char buf[BUFSIZ];
uid_t ruid;
uid_t euid;
uid_t suid;
void myseteuid(int);
void printUids(const char *);
/* We're using this wrapper function, because the behavior
* of seteuid(2) with respect to the saved-set-uid is inconsistent
* across platforms. On e.g. NetBSD, the POSIX.1-2017 mandated
* behavior is not implemented; see the note in the manual page
* as well as in <unistd.h>. */
void
myseteuid(int myeuid) {
char *func = "seteuid(";
#ifdef _POSIX_SAVED_IDS
if (seteuid(myeuid) == -1) {
fprintf(stderr, "Unable to seteuid(%d): %s\n", myeuid, strerror(errno));
exit(EXIT_FAILURE);
/* NOTREACHED */
}
#else
if (setreuid(-1, myeuid) == -1) {
fprintf(stderr, "Unable to setreuid(-1, %d): %s\n", myeuid, strerror(errno));
exit(EXIT_FAILURE);
/* NOTREACHED */
}
func = "setreuid(-1, ";
#endif
if (snprintf(buf, BUFSIZ, "After %s%d)", func, myeuid) < 0) {
fprintf(stderr, "Unable to snprintf: %s\n", strerror(errno));
exit(EXIT_FAILURE);
/* NOTREACHED */
}
printUids(buf);
}
/* On e.g. Linux, we can get the saved set-uid
* via getresuid(2); not all systems support
* this, however. On those that do not,
* we use the suid value initialized at program
* startup.
*/
void
printUids(const char *msg) {
ruid = getuid();
euid = geteuid();
#ifdef _GNU_SOURCE
if (getresuid(&ruid, &euid, &suid) < 0) {
fprintf(stderr, "Unable to getresuid: %s\n", strerror(errno));
exit(EXIT_FAILURE);
/* NOTREACHED */
}
#endif
printf("%s: ruid %d, euid %d, suid %d\n", msg, ruid, euid, suid);
}
int
main(int argc, char **argv) {
int fd;
if (argc != 2) {
fprintf(stderr, "%s: Usage: %s filename\n", argv[0], argv[0]);
exit(EXIT_FAILURE);
/* NOTREACHED */
}
ruid = getuid();
euid = geteuid();
/* fake saved-setuid */
suid = geteuid();
printUids("Program start");
printf("We're privileged; let's set all UIDs to another account.\n");
myseteuid(UNPRIVILEGED_UID);
printf("We're unprivileged, but with the help of the saved set-uid, we can regain initial setuid (%d) privs.\n", suid);
printf("But let's drop them again via seteuid(%d)...\n", ruid);
myseteuid(ruid);
printf("Trying to open a privileged file...\n");
if ((fd = open(argv[1], O_RDONLY)) == -1) {
fprintf(stderr, "Unable to open %s: %s\n",
argv[1], strerror(errno));
}
/* We don't do anything with the file, we just demonstrated
* that we were unable to open it. */
(void)close(fd);
printf("Ok, let's try with elevated privileges.\n");
myseteuid(suid);
if ((fd = open(argv[1], O_RDONLY)) == -1) {
fprintf(stderr, "Unable to open %s: %s\n",
argv[1], strerror(errno));
exit(EXIT_FAILURE);
/* NOTREACHED */
} else {
printf("Opening worked.\n");
/* Now we could do stuff with 'fd', if we were
* so inclined. We're not, though. */
(void)close(fd);
}
printf("Alright, we're done using our elevated privileges. Let's drop them permanently.\n");
if (setuid(ruid) == -1) {
fprintf(stderr, "Unable to setuid: %s\n", strerror(errno));
exit(EXIT_FAILURE);
/* NOTREACHED */
}
if (snprintf(buf, BUFSIZ, "After setuid(%d)", ruid) < 0) {
fprintf(stderr, "Unable to snprintf: %s\n", strerror(errno));
exit(EXIT_FAILURE);
/* NOTREACHED */
}
printUids(buf);
printf("Now trying to gain elevated privileges again.\n");
/* Trying to gain elevated privileges again
* should fail here, because setuid(2), called
* above, is supposed to set the real and effective
* uid as well as the saved set-user ID.
*
* However, the results are platform dependent,
* based on whether or not the euid at
* execution start was 0 or not. See
* https://is.gd/QVO35q for details.
*
* In practice, we observe that:
* - NetBSD sets the saved set-user-ID in setuid(2)
* even for non-root
* - OS X and Linux do NOT set the saved set-user-ID
* in setuid(2) IF the euid was non-zero
*/
if (seteuid(suid) != -1) {
#ifdef _POSIX_SAVED_IDS
if (euid == 0) {
fprintf(stderr, "seteuid(%d) should not have succeeded!\n", euid);
}
#endif
}
if (snprintf(buf, BUFSIZ, "After attempted seteuid(%d)", suid) < 0) {
fprintf(stderr, "Unable to snprintf: %s\n", strerror(errno));
exit(EXIT_FAILURE);
/* NOTREACHED */
}
printUids(buf);
printf("(We expect the following call to open(2) to fail if setuid was 0 initially.)\n");
if ((fd = open(argv[1], O_RDONLY)) == -1) {
fprintf(stderr, "Unable to open %s: %s\n",
argv[1], strerror(errno));
} else {
if (euid == 0) {
printf("Wait, what? This shouldn't have worked!\n");
}
(void)close(fd);
}
exit(EXIT_SUCCESS);
}