Skip to content

Instantly share code, notes, and snippets.

@webstrand
Last active March 8, 2024 14:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save webstrand/4539759440b9ddec3592f770594ea385 to your computer and use it in GitHub Desktop.
Save webstrand/4539759440b9ddec3592f770594ea385 to your computer and use it in GitHub Desktop.
SUID binary allowing unpriviliged users to enter permitted network namespaces
#define _GNU_SOURCE
#include <stdio.h>
#include <sched.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
void autofree(char **str) {
if(str != NULL && *str != NULL) free(*str);
}
void autoclose(int *fd) {
if(fd != NULL && *fd >= 0) close(*fd);
}
int main(int argc, char *argv[]) {
if(argc < 3) {
fprintf(stderr, "Usage: %s netns program [...arguments]\n", argv[0]);
return 1;
}
if(strchr(argv[1], '/') != NULL) {
fprintf(stderr, "network namespace name must not contain slashes");
return 1;
}
int netns_grant_fd __attribute__((cleanup(autoclose))) = openat(-1, "/run/netns-grant", O_PATH | O_DIRECTORY | O_CLOEXEC);
if(netns_grant_fd < 0) {
fprintf(stderr, "openat(-1, \"/run/netns-grant\"): %s\n", strerror(errno));
return 1;
}
struct stat sb;
if(fstat(netns_grant_fd, &sb) < 0) {
perror("fstat");
return 1;
}
if(sb.st_uid != 0 || sb.st_mode & ~0744 != 0) {
fprintf(stderr, "/run/netns-grant has invalid permissions: must be owned by root and writable only by root");
return 1;
}
char *user_path __attribute__((cleanup(autofree))) = NULL;
if(asprintf(&user_path, "%" PRIdMAX, (intmax_t) geteuid()) < 0) {
user_path = NULL; // contents are undefined after asprintf
perror("asprintf");
return 1;
}
// Open the "/run/netns-grant/{user-id}" directory so that we can safely lookup
// the requested netns name.
int netns_grant_user_fd __attribute__((cleanup(autoclose))) = openat(netns_grant_fd, user_path, O_PATH | O_DIRECTORY | O_CLOEXEC);
if(netns_grant_user_fd < 0) {
fprintf(stderr, "openat(%d, \"%s\"): %s\n", netns_grant_fd, user_path, strerror(errno));
return 1;
}
if(fstat(netns_grant_user_fd, &sb) < 0) {
perror("fstat");
return 1;
}
if(sb.st_uid != 0 || sb.st_mode & ~0744 != 0) {
fprintf(stderr, "/run/netns-grant/{user-id} has invalid permissions: must be owned by root and writable only by root");
return 1;
}
// Open the requested "/run/netns-grand/{user-id}/{argv[1]}" netns if it exists.
int netns_fd __attribute__((cleanup(autoclose))) = openat(netns_grant_user_fd, argv[1], O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
if(netns_fd < 0) {
fprintf(stderr, "openat(%d, \"%s\"): %s\n", netns_grant_user_fd, argv[1], strerror(errno));
return 1;
}
// Switch into the netns, this will fail if the opened file is not a network namespace.
if(setns(netns_fd, CLONE_NEWNET) < 0) {
perror("setns");
return 1;
}
execvp(argv[2], &argv[2]);
perror("execvp");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment