Files
quickdev16/tools/zsnes/src/linux/safelib.c
2009-04-22 20:04:28 +02:00

356 lines
7.9 KiB
C

/*
Copyright (C) 1997-2007 ZSNES Team ( zsKnight, _Demo_, pagefault, Nach )
http://www.zsnes.com
http://sourceforge.net/projects/zsnes
https://zsnes.bountysource.com
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
version 2 as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "gblhdr.h"
#include <sys/param.h>
#include <sys/wait.h>
#include <signal.h>
#include <paths.h>
#include <grp.h>
#include <pwd.h>
#ifndef OPEN_MAX
#define OPEN_MAX 256
#endif
#include "safelib.h"
#include "../argv.h"
//C++ style code in C
#define bool unsigned char
#define true 1
#define false 0
//Introducing secure forking ;) -Nach
//Taken from the secure programming cookbook, somewhat modified
static bool spc_drop_privileges()
{
gid_t newgid = getgid(), oldgid = getegid();
uid_t newuid = getuid(), olduid = geteuid();
char *name = getlogin();
struct passwd *userinfo;
if (!olduid && name && (userinfo = getpwnam(name)) && userinfo->pw_uid)
{
setgroups(1, &userinfo->pw_gid);
#if !defined(linux)
setegid(userinfo->pw_gid);
if (setgid(userinfo->pw_gid) == -1) { return(false); }
#else
if (setregid(userinfo->pw_gid, userinfo->pw_gid) == -1) { return(false); }
#endif
#if !defined(linux)
seteuid(userinfo->pw_uid);
if (setuid(userinfo->pw_uid) == -1) { return(false); }
#else
if (setreuid(userinfo->pw_uid, userinfo->pw_uid) == -1) { return(false); }
#endif
if ((setegid(oldgid) != -1) || (getegid() != userinfo->pw_gid)) { return(false); }
if ((seteuid(olduid) != -1) || (geteuid() != userinfo->pw_uid)) { return(false); }
}
else
{
//If root privileges are to be dropped, be sure to pare down the ancillary
//groups for the process before doing anything else because the setgroups()
//system call requires root privileges. Drop ancillary groups regardless of
//whether privileges are being dropped temporarily or permanently.
if (!olduid) setgroups(1, &newgid);
if (newgid != oldgid)
{
#if !defined(linux)
setegid(newgid);
if (setgid(newgid) == -1) { return(false); }
#else
if (setregid(newgid, newgid) == -1) { return(false); }
#endif
}
if (newuid != olduid)
{
#if !defined(linux)
seteuid(newuid);
if (setuid(newuid) == -1) { return(false); }
#else
if (setreuid(newuid, newuid) == -1) { return(false); }
#endif
}
//verify that the changes were successful
if (newgid != oldgid && (setegid(oldgid) != -1 || getegid() != newgid)) { return(false); }
if (newuid != olduid && (seteuid(olduid) != -1 || geteuid() != newuid)) { return(false); }
}
return(true);
}
static int open_devnull(int fd)
{
FILE *f = 0;
if (!fd) { f = freopen(_PATH_DEVNULL, "rb", stdin); }
else if (fd == 1) { f = freopen(_PATH_DEVNULL, "wb", stdout); }
else if (fd == 2) { f = freopen(_PATH_DEVNULL, "wb", stderr); }
return(f && fileno(f) == fd);
}
static bool array_contains(int *a, size_t size, int key)
{
size_t i;
for (i = 0; i < size; i++)
{
if (a[i] == key) { return(true); }
}
return(false);
}
static bool spc_sanitize_files(int *a, size_t size, int skip)
{
int fd, fds;
struct stat st;
//Make sure all open descriptors other than the standard ones are closed
if ((fds = getdtablesize()) == -1)
{
fds = OPEN_MAX;
}
for (fd = 3; fd < fds; fd++)
{
if ((fd != skip) && !array_contains(a, size, fd)) { close(fd); }
}
//Verify that the standard descriptors are open. If they're not, attempt to
//open them using /dev/null. If any are unsuccessful, fail.
for (fd = 0; fd < 3; fd++)
{
if (fstat(fd, &st) == -1 && (errno != EBADF || !open_devnull(fd)))
{
return(false);
}
}
return(true);
}
//Pass array of file descriptors to leave open
pid_t safe_fork(int *a, size_t size)
{
int filedes[2];
if (!pipe(filedes))
{
char success = 0;
pid_t childpid;
if ((childpid = fork()) == -1) //Fork Failed
{
close(filedes[0]);
close(filedes[1]);
return(-1);
}
if (childpid) //Parent Process
{
close(filedes[1]); //Close writing
read(filedes[0], &success, 1);
close(filedes[0]);
if (success)
{
return(childpid);
}
waitpid(childpid, 0, 0);
return(-1);
}
//This is the child proccess
close(filedes[0]); //Close reading
if (!spc_sanitize_files(a, size, filedes[1]) || !spc_drop_privileges())
{
write(filedes[1], &success, 1);
close(filedes[1]);
_exit(0);
}
success = 1;
write(filedes[1], &success, 1);
close(filedes[1]);
return(0);
}
return(-1);
}
//Introducing a popen which doesn't return until it knows for sure of program launched or couldn't open -Nach
//Forks, parent is paused until child successfully execs (returns child pid) or child exits (returns failure)
static pid_t parent_pause_fork()
{
int filedes[2];
if (!pipe(filedes))
{
int pid = fork();
if (pid == -1) //Failed
{
close(filedes[0]);
close(filedes[1]);
}
else if (pid > 0) //Parent
{
char success = 1;
close(filedes[1]);
read(filedes[0], &success, 1);
close(filedes[0]);
if (success)
{
return(pid);
}
waitpid(pid, 0, 0);
}
else //Child
{
close(filedes[0]);
fcntl(filedes[1], F_SETFD, FD_CLOEXEC);
return(-filedes[1]);
}
}
return(0);
}
static void close_child(pid_t pid)
{
char success = 0;
write(-pid, &success, 1);
close(-pid);
_exit(0);
}
#define IS_PARENT(x) ((x) > 0)
#define IS_CHILD(x) ((x) < 0)
#define IS_FAIL(x) ((x) == 0)
static struct fp_pid_link
{
FILE *fp;
pid_t pid;
struct fp_pid_link *next;
} fp_pids = { 0, 0, 0 };
FILE *safe_popen(char *command, const char *mode)
{
//filedes[0] is for reading
//filedes[1] is for writing.
int filedes[2];
if (mode && (*mode == 'r' || *mode == 'w') && !pipe(filedes))
{
pid_t childpid = parent_pause_fork();
if (IS_PARENT(childpid))
{
FILE *fp;
if (*mode == 'r')
{
close(filedes[1]);
fp = fdopen(filedes[0], "r");
}
else
{
close(filedes[0]);
fp = fdopen(filedes[1], "w");
}
if (fp)
{
struct fp_pid_link *link = &fp_pids;
while (link->next)
{
link = link->next;
}
link->next = (struct fp_pid_link *)malloc(sizeof(struct fp_pid_link));
if (link->next)
{
link->next->fp = fp;
link->next->pid = childpid;
link->next->next = 0;
return(fp);
}
fclose(fp);
}
kill(childpid, SIGTERM);
waitpid(childpid, 0, 0);
}
else if (IS_CHILD(childpid))
{
char **argv = build_argv(command);
if (argv)
{
if (*mode == 'r')
{
dup2(filedes[1], STDOUT_FILENO);
}
else
{
dup2(filedes[0], STDIN_FILENO);
}
if (spc_sanitize_files(0, 0, -childpid) && spc_drop_privileges())
{
execvp(argv[0], argv);
}
free(argv);
}
close_child(childpid);
}
close(filedes[0]);
close(filedes[1]);
}
return(0);
}
void safe_pclose(FILE *fp)
{
struct fp_pid_link *link = &fp_pids;
while (link->next && link->next->fp != fp)
{
link = link->next;
}
if (link->next->fp == fp)
{
struct fp_pid_link *dellink = link->next;
fclose(fp);
waitpid(link->next->pid, 0, 0);
link->next = link->next->next;
free(dellink);
}
}