-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Attached is the C source that is my first attempt at a mock launcher
program. This program is built as the executable /usr/bin/mock with
setuid:root, setgid:mock and will exec python with the input file
/usr/bin/mock.py.  The program successfully  launches python, but I
haven't worked through all the issues to complete a build (yet). I
know that mock.py needs to be modified to remove the dont-run-as-root
test and I've made a first cut at removing mock-helper from .cfg
files, but I'm sure there are other things that need to be done. I
have checked nothing into CVS at this point and even if/when I do, I
suspect I'll be working off a branch for a while.

Note that the program makes use of Linux namespaces. This *should*
make our handling of mount points within the chroot (/proc, /sys,
etc.) a bit easier to clean up, since when the process dies the mounts
should just go away. I haven't verified this though, so caveat emptor.

Anyway, it's a starting point. Please look it over and provide feedback.

Clark

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.3 (GNU/Linux)
Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org

iD8DBQFEkC9DHyuj/+TTEp0RAq+4AKCn4hj3bGInJBMWd1Bj6h1SMeCV+QCgt3NW
hPHE9hW9DXL9ZkPLbaJrL6Y=
=XtsC
-----END PGP SIGNATURE-----

//
// mock.c - setuid program for launching mock.py
//
// 
// 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 Library 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
// Copyright (c) 2006 - Clark Williams <[EMAIL PROTECTED]>
//   portions lifted from mock-helper.c by Seth Vidal
//   namespace idea courtesy Enrico Scholz <[EMAIL PROTECTED]>

//#define _GNU_SOURCE

#include "config.h"

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <sched.h>
#include <assert.h>
#include <asm/unistd.h>
#include <signal.h>

#ifdef USE_SELINUX
#include <selinux/selinux.h>
#endif

#define PYTHON_PATH	"/usr/bin/python"
#define MOCK_PATH	"/usr/bin/mock.py"

static char const * const ALLOWED_ENV[] =
{
	"dist",
	"ftp_proxy", 
	"http_proxy", 
	"https_proxy", 
	"no_proxy", 
	"PS1",
};

#define ALLOWED_ENV_SIZE (sizeof (ALLOWED_ENV) / sizeof (ALLOWED_ENV[0]))
#define SAFE_PATH	"PATH=/bin:/usr/bin:/usr/sbin"
#define SAFE_HOME	"HOME=/root"
#define NSFLAG		"NAMESPACE=1"

// Note that MAX_ENV_SIZE is allowed size, plus the ones we add plus a null entry
// so, if you add more variables, increase the constant below!
#define MAX_ENV_SIZE	4 + ALLOWED_ENV_SIZE

//
// helper functions
//

void usage ()
{
	printf ("Usage: mock [command]\n");
	exit (1);
}

//
// print formatted string to stderr and terminate
//
void error (const char *format, ...)
{
	va_list ap;
	
	va_start (ap, format);
	fprintf (stderr, "mock: error: ");
	vfprintf (stderr, format, ap);
	va_end (ap);
	fprintf (stderr, "\n");
	exit (1);
}

//
// debug print
//
void debug(const char *format, ...)
{
#ifdef DEBUG
	va_list ap;
	
	va_start (ap, format);
	fprintf (stderr, "DEBUG: ");
	vfprintf (stderr, format, ap);
	va_end (ap);
#endif
}

///////////////////////////////////////////
//
// main logic
//
//////////////////////////////////////////

int main (int argc, char **argv)
{
	char *env[MAX_ENV_SIZE];
	char **newargv;
	int newargc, newargvsz;
	int i, idx = 2;
	int status;
	pid_t pid;

	// catch the simple no arguments invocation
	if (argc < 2)
		usage();
	
	// elevate our privileges 
	if (setreuid (geteuid (), geteuid ()))
		error("setreuid failed: %s", strerror(errno));
	debug("running with uid: %d, gid: %d\n", getuid(), getgid());

#ifdef USE_SELINUX
	// add LD_PRELOAD for our selinux lib if selinux is in use is set
	if ((is_selinux_enabled() > 0) && (use_selinux_preload == 1)) {
		ld_preload = strdup("LD_PRELOAD=libselinux-mock.so");
		debug("adding ld_preload of %s\n", ld_preload);
		env[idx++] = ld_preload;
	}
#endif

	// copy in allowed environment variables to our environment
	debug("copying envionment\n");
	memset(env, '\0', sizeof(char *) * (3 + ALLOWED_ENV_SIZE));
	env[0] = strdup(SAFE_PATH);
	env[1] = strdup(SAFE_HOME);
	env[2] = strdup(NSFLAG);
	for (i = 0; i < ALLOWED_ENV_SIZE; ++i)
	{
		char *ptr = getenv (ALLOWED_ENV[i]);
		if (ptr==0) continue;
		ptr -= strlen (ALLOWED_ENV[i]) + 1;
		env[idx++] = ptr;
	}
	assert(idx <= MAX_ENV_SIZE);

	// set up a new argv/argc
	//     new argv[0] will be "/usr/bin/python"
	//     new argv[1] will be "/usr/bin/mock.py"
	//     remainder of new argv will be old argv[1:n]
	//     allocate one extra for null at end
	newargc = argc + 1;
	newargvsz = sizeof(char *) * (newargc + 1);
	newargv = malloc(newargvsz);
	if (newargv == NULL)
		error("malloc of argument vector (%d bytes) failed!\n", newargvsz);
	memset(newargv, '\0', newargvsz);
	newargv[0] = strdup(PYTHON_PATH);
	debug("argv[0] = %s\n", newargv[0]);
	newargv[1] = strdup(MOCK_PATH);
	debug("argv[1] = %s\n", newargv[1]);
	for (i = 1; i < argc; i++) {
		newargv[i+1] = argv[i];
		debug("argv[%d] = %s\n", i+1, newargv[i+1]);
	}

	// clone a new process with a separate namespace
	// Note: we have to use syscall here, since we want the
	//       raw system call 'clone', not the glibc library wrapper
	// Also note: The SIGCHLD or'ed into the flags argument. If you
	// don't specify an exit signal, the child is detached and waitpid
	// won't work (how the heck Enrico figured it out is beyond me, since
	// there are only two mentions of CSIGNAL in fork.c...)
	debug("cloning new namespace\n");
	pid = syscall(__NR_clone, CLONE_VFORK|CLONE_NEWNS|SIGCHLD, 0);

	// urk! no clone?
	if (pid == -1)
		error("clone failed: %s\n", strerror(errno));

	// exec python
	if (pid == 0) {
		debug("exec'ing python\n");
		execve(PYTHON_PATH, newargv, env);
		error("execve failed: %s\n", strerror(errno));
	}
	
	// wait for the child to finish and exit appropriately
	debug("waiting for child to finish\n");
	if (waitpid(pid, &status, 0) != pid)
		error("waitpid failed: %s\n", strerror(errno));
	if (WIFEXITED(status)) {
		debug("Exiting with status 0x%x (0x%x)\n", WEXITSTATUS(status), status);
		exit(WEXITSTATUS(status));
	}
	if (WIFSIGNALED(status))
		error("errored out with signal %d\n", WTERMSIG(status));

	exit(-1);	// WTF? how did we get here?
}
--
Fedora-buildsys-list mailing list
Fedora-buildsys-list@redhat.com
https://www.redhat.com/mailman/listinfo/fedora-buildsys-list

Reply via email to