Improved spc_fork to remove all bugs (take that cookbook!). Split out code from sdllink to a seperate file. Added a safe popen() which doesn't return until it knows the application is there or not, and doesn't carry around extra data, can be closed with fclose(). Fixed some warnings. And there was much rejoicing across the land.

This commit is contained in:
n-a-c-h
2006-03-01 00:34:15 +00:00
parent e54dd4668d
commit 3e93feacfc
5 changed files with 452 additions and 119 deletions

View File

@@ -72,7 +72,7 @@ VIDEOBJ=${VIDEODIR}/makev16b.o ${VIDEODIR}/makev16t.o ${VIDEODIR}/makevid.o\
${VIDEODIR}/hq4x16.o ${VIDEODIR}/hq4x32.o
WINOBJ=${WINDIR}/copyvwin.o ${WINDIR}/sdlintrf.o ${WINDIR}/sdllink.o @GL_DRAW@\
${WINDIR}/sw_draw.o ${WINDIR}/zfilew.o
${WINDIR}/sw_draw.o ${WINDIR}/zfilew.o ${WINDIR}/safelib.o
WINDOSOBJ=${DOSDIR}/debug.o ${DOSDIR}/joy.o ${DOSDIR}/vesa2.o
@@ -154,7 +154,7 @@ uic.o: uic.c asm_call.h gblhdr.h
vcache.o: vcache.asm macros.mac
version.o: version.c
zloader.o: zloader.c gblhdr.h
zmovie.o: zmovie.c asm_call.h gblhdr.h gblvars.h numconv.h md.o
zmovie.o: zmovie.c asm_call.h gblhdr.h gblvars.h numconv.h ${WINDIR}/safelib.h md.o
zstate.o: zstate.c asm_call.h gblhdr.h gblvars.h numconv.h
${CHIPDIR}/c4emu.o: ${CHIPDIR}/c4emu.c gblhdr.h
@@ -271,9 +271,10 @@ ${WINDIR}/copyvwin.o: ${WINDIR}/copyvwin.asm macros.mac
${WINDIR}/gl_draw.o: ${WINDIR}/gl_draw.c gblhdr.h
${WINDIR}/sdlintrf.o: ${WINDIR}/sdlintrf.asm macros.mac
${WINDIR}/sdllink.o: ${WINDIR}/sdllink.c asm_call.h gblhdr.h ${WINDIR}/sw_draw.h\
${WINDIR}/gl_draw.h
${WINDIR}/gl_draw.h ${WINDIR}/safelib.h
${WINDIR}/sw_draw.o: ${WINDIR}/sw_draw.c gblhdr.h
${WINDIR}/zfilew.o: ${WINDIR}/zfilew.c
${WINDIR}/safelib.o: ${WINDIR}/safelib.c ${WINDIR}/safelib.h
${ZIPDIR}/unzip.o: ${ZIPDIR}/unzip.c gblhdr.h ${ZIPDIR}/zunzip.h
${ZIPDIR}/zpng.o: ${ZIPDIR}/zpng.c gblhdr.h ${ZIPDIR}/zpng.h

406
zsnes/src/linux/safelib.c Normal file
View File

@@ -0,0 +1,406 @@
/*
Copyright (C) 1997-2006 ZSNES Team ( zsKnight, _Demo_, pagefault, Nach )
http://www.zsnes.com
http://sourceforge.net/projects/zsnes
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later
version.
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>
#ifndef OPEN_MAX
#define OPEN_MAX 256
#endif
#include "safelib.h"
//C++ style code in C
#define bool unsigned char
#define true 1
#define false 0
//Introducing the secure browser opener for POSIX systems ;) -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();
/* 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);
}
static bool child_exited;
//Pass array of file descriptors to leave open
pid_t spc_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]);
return(success ? childpid : -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
static char *decode_string(char *str)
{
size_t str_len = strlen(str), i = 0;
char *dest = str;
if ((str_len > 1) && ((*str == '\"') || (*str == '\'')) && (str[str_len-1] == *str))
{
memmove(str, str+1, str_len-2);
str[str_len-2] = 0;
}
while (*str)
{
if (*str == '\\')
{
str++;
}
dest[i++] = *str++;
}
dest[i] = 0;
return(dest);
}
static char *find_next_match(char *str, char match_char)
{
char *pos = 0;
while (*str)
{
if (*str == match_char)
{
pos = str;
break;
}
if (*str == '\\')
{
if (str[1])
{
str++;
}
else
{
break;
}
}
str++;
}
return(pos);
}
static char *get_param(char *str)
{
static char *pos = 0;
char *token = 0;
if (str) //Start a new string?
{
pos = str;
}
if (pos)
{
//Skip delimiters
while (*pos == ' ') { pos++; }
if (*pos)
{
token = pos;
//Skip non-delimiters
while (*pos && (*pos != ' '))
{
//Skip quoted characters
if ((*pos == '\"') || (*pos == '\''))
{
char *match_pos = 0;
if ((match_pos = find_next_match(pos+1, *pos)))
{
pos = match_pos;
}
}
//Skip escaped spaces
if (*pos == '\\') { pos++; }
pos++;
}
if (*pos) { *pos++ = '\0'; }
}
}
return(token);
}
static size_t count_param(char *str)
{
size_t i = 0;
while (*str)
{
//Skip delimiters
while (*str == ' ') { str++; }
//Skip non-delimiters
while (*str && (*str != ' '))
{
//Skip quoted characters
if ((*str == '\"') || (*str == '\''))
{
char *match_str = 0;
if ((match_str = find_next_match(str+1, *str)))
{
str = match_str;
}
}
//Skip escaped spaces
if (*str == '\\') { str++; }
str++;
}
i++;
}
return(i);
}
static char **build_argv(char *str)
{
size_t argc = count_param(str);
char **argv = (char **)malloc(sizeof(char *)*(argc+1));
if (argv)
{
char *p, **argp = argv;
for (p = get_param(str); p; p = get_param(0), argp++)
{
*argp = decode_string(p);
}
*argp = 0;
return(argv);
}
return(0);
}
static void argv_print(char **argv)
{
char **argp = argv;
while (*argp)
{
printf("argv[%u]: %s\n", argp-argv, *argp);
argp++;
}
printf("argv[%u]: NULL\n", argp-argv);
}
static bool child_exited;
void catch_child(int sig_num)
{
int child_status;
wait(&child_status);
signal(SIGCHLD, SIG_IGN);
child_exited = true;
}
FILE *safe_popen(char *command, const char *mode)
{
//filedes[0] is for reading
//filedes[1] is for writing.
int filedes[2];
if ((*mode == 'r' || *mode == 'w') && !pipe(filedes))
{
char **argv = build_argv(command);
if (argv)
{
pid_t childpid;
child_exited = false;
signal(SIGCHLD, catch_child);
if ((childpid = vfork()) == -1) //Fork Failed
{
signal(SIGCHLD, SIG_IGN);
free(argv);
close(filedes[0]);
close(filedes[1]);
return(0);
}
if (childpid) //Parent
{
FILE *fp;
signal(SIGCHLD, SIG_IGN);
free(argv);
if (!child_exited)
{
if (*mode == 'r')
{
close(filedes[1]);
fp = fdopen(filedes[0], "r");
}
else
{
close(filedes[0]);
fp = fdopen(filedes[1], "w");
}
if (fp) { return(fp); }
}
close(filedes[0]);
close(filedes[1]);
return(0);
}
//Child
if (*mode == 'r')
{
dup2(filedes[1], STDOUT_FILENO);
close(filedes[0]);
}
else
{
dup2(filedes[0], STDIN_FILENO);
close(filedes[1]);
}
execvp(argv[0], argv);
_exit(0);
}
close(filedes[0]);
close(filedes[1]);
}
return(0);
}

37
zsnes/src/linux/safelib.h Normal file
View File

@@ -0,0 +1,37 @@
/*
Copyright (C) 1997-2006 ZSNES Team ( zsKnight, _Demo_, pagefault, Nach )
http://www.zsnes.com
http://sourceforge.net/projects/zsnes
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later
version.
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.
*/
#ifndef SAFELIB_H
#define SAFELIB_H
#include <stdio.h>
#include <sys/types.h>
pid_t spc_fork(int *, size_t);
FILE *safe_popen(char *, const char *);
void safe_pclose(FILE *);
#define popen safe_popen
#define pclose fclose
#endif

View File

@@ -30,19 +30,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#include <time.h>
#include <dirent.h>
#include <sys/param.h>
#include <paths.h>
#include <grp.h>
#ifndef OPEN_MAX
#define OPEN_MAX 256
#endif
//C++ style code in C
#define bool unsigned char
#define true 1
#define false 0
#include "safelib.h"
#include "../asm_call.h"
@@ -1200,107 +1188,6 @@ float sem_GetTicks()
//Introducing the secure browser opener for POSIX systems ;) -Nach
//Taken from the secure programming cookbook, slightly modified
bool spc_drop_privileges() {
gid_t newgid = getgid(), oldgid = getegid();
uid_t newuid = getuid(), olduid = geteuid();
/* 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);
}
void spc_sanitize_files() {
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++) close(fd);
/* Verify that the standard descriptors are open. If they're not, attempt to
* open them using /dev/null. If any are unsuccessful, abort.
*/
for (fd = 0; fd < 3; fd++)
if (fstat(fd, &st) == -1 && (errno != EBADF || !open_devnull(fd))) abort();
}
pid_t spc_fork() {
pid_t childpid;
if ((childpid = fork()) == -1) return -1;
//If this us the parent proccess nothing more to do
if (childpid != 0) return childpid;
//This is the child proccess
spc_sanitize_files();
/*
There actually is a bug here which I submitted to the authors of the book -Nach
The bug is as follows:
The parent returns the child proccess ID in event of success.
The child returns 0 on success if and only if it's spc_drop_privileges() call is successful.
It is possible that the parent will return with a pid > 0, while the child never returns
from spc_fork thus causing a programming error.
The function should be rewritten that the parent doesn't return till it knows if the
child is able to return or not. And then return -1 or the child pid.
For out purposes in ZSNES to launch a browser, this bug does not effect us. But
be careful if you copy this code to use somewhere else.
*/
if (!spc_drop_privileges()) //Failed to drop special privleges
{
_exit(0);
}
return 0;
}
void LaunchBrowser(char *browser, char *url)
{
char *const arglist[] = { browser, url, 0 };
@@ -1309,7 +1196,7 @@ void LaunchBrowser(char *browser, char *url)
void LaunchURL(char *url)
{
if (spc_fork()) //If fork failed, or we are the parent
if (spc_fork(0, 0)) //If fork failed, or we are the parent
{
MouseX = 0;
MouseY = 0;

View File

@@ -22,6 +22,8 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#ifdef __UNIXSDL__
#include "gblhdr.h"
#include "linux/safelib.h"
#include <sys/poll.h>
#define DIR_SLASH "/"
#define WRITE_BINARY "w"
#else
@@ -2010,7 +2012,7 @@ static bool raw_video_open()
break;
case 2: case 3:
mencoderExists = raw_vid.vp = popen(encode_command(md_command), WRITE_BINARY);
mencoderExists = (unsigned char)(int)(raw_vid.vp = popen(encode_command(md_command), WRITE_BINARY));
break;
default: