lx_thunk.c revision 7257d1b4d25bfac0c802847390e98a464fd787ac
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* The BrandZ Linux thunking library.
*
* The interfaces defined in this file form the client side of a bridge
* to allow native Solaris process to access Linux services. Currently
* the Linux services that is made accessible by these interfaces here
* are:
* - Linux host <-> address naming services
* - Linux service <-> port naming services
* - Linux syslog
*
* Currently, to use this library it must be LD_PRELOADed into the
* application that needs to access Linux services. Once loaded
* Linux services are accessed by the client application in two
* different ways:
*
* - Direct library calls:
* lxt_gethostbyname_r
* lxt_gethostbyaddr_r
* lxt_getservbyname_r
* lxt_getservbyport_r
* lxt_debug
*
* These library functions are used by the BrandZ lx name services
* translation library (lx_nametoaddr.so) to handle libnsl.so name
* service requests.
*
* - Intercepted library calls:
* openlog(3c)
* syslog(3c)
* vsyslog(3c)
* closelog(3c)
*
* Via the LD_PRELOAD mechanism this library interposes itself on
* these interfaces and when the application calls these interfaces
* (either directly or indirectly via any libraries the program may
* be linked against) this library intercepts the request and passes
* it onto a Linux process to handle the request.
*
* Once this library receives a request that needs to be serviced by a
* Linux process, it packs up that request and attempts to send it
* to a doors server. The door server interfaces are defined in
* lx_thunk_server.h. If the doors server is not running or not
* responding, this library will attempt to spawn a new doors server
* by forking and executing the following shell script (which runs as
*
* Notes:
* - This library also intercepts the following system calls:
* close(2) - We intercept close(2) to prevent the caller from
* accidentally closing any of the file descriptors we
* need to do our work.
*
* setppriv(2) - We intercept setppriv(2) to prevent a process
* from dropping any of the privileges we'll need to create
* a new lx_thunk server process and to deal with service
* requests.
*
* - To facilitate the running of native Solaris programs and libraries
* when this library is preloaded into an application it will chroot()
* into /native. This way the Solaris application and libraries can
* access files via their expected paths and we can avoid having to
* either do path mapping or modifying all libraries to make them
* aware of "/native" so that they can pre-pend it to all their
* filesystem operations.
*
* - This library can only be used with processes that are initially
* run by root in a zone. The reason is that we use the chroot()
* system call and this requires the PRIV_PROC_CHROOT privilege,
* which non-root users don't have.
*/
#include <alloca.h>
#include <assert.h>
#include <dlfcn.h>
#include <door.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <netdir.h>
#include <priv.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <synch.h>
#include <sys/lx_thunk_server.h>
#include <sys/lx_thunk.h>
#include <sys/priv_impl.h>
#include <thread.h>
#include <unistd.h>
#define LXT_DOOR_DIR "/tmp"
#define LXT_DOOR_PREFIX "lxt"
static char lxt_debug_path_buf[MAXPATHLEN];
static int root_fd;
static int debug_fd = -1;
void
init(void)
{
/* check if there's a debug log file specified */
if (lxt_debug_path == NULL) {
lxt_debug_path = "/dev/tty";
}
sizeof (lxt_debug_path_buf));
/*
* Open the debugging output file. We need to open it
* and hold it open because we're going to call chroot()
* in just a second, so we won't be able to open it later.
*/
0666)) != -1) {
}
}
lxt_debug("lxt_init: executing native process");
/* Get a fd that points to the root directory */
lxt_debug("lxt_init(): "
exit(-1);
}
/*
* Now, so that we can avoid having to do path mapping,
* just chdir() and chroot() into /native.
*/
if (chdir("/native") != 0) {
lxt_debug("lxt_init(): "
exit(-1);
}
if (chroot("/native") != 0) {
lxt_debug("lxt_init(): "
exit(-1);
}
}
/*
* Linux Thunking Interfaces - Client Side
*/
static int lxt_door_fd = -1;
static void
{
extern const char **environ;
lxt_debug("lxt_server_exec: server starting");
/*
* First we need to dup our fifos to the file descriptors
* the brand library is expecting them to be at.
*/
/* Check if the write fifo needs to be moved aside */
if ((fifo_wr == LXT_SERVER_FIFO_RD_FD) &&
return;
/* Check if the read fifo needs to be moved aside */
if ((fifo_rd == LXT_SERVER_FIFO_WR_FD) &&
return;
if ((fifo_wr != LXT_SERVER_FIFO_WR_FD) &&
return;
if ((fifo_rd != LXT_SERVER_FIFO_RD_FD) &&
return;
/*
* We're about to execute a native Linux process.
* Since we've been loaded into a Solaris process with
* LD_PRELOAD and LD_LIBRARY_PATH we should clear these
* variables from the environment before calling exec.
*/
(void) unsetenv("LD_PRELOAD");
(void) unsetenv("LD_LIBRARY_PATH");
/*
* Now we need to exec the thunk server process. This is a
* branded Linux process that will act as a doors server and
* service our requests to perform native Linux operations.
* Since we're currently running as a native Solaris process
* to start up the server we'll use the brand system call to
* the kernel that the target of the exec will be a branded
* process.
*/
lxt_debug("lxt_server_exec: execing as Linux process");
}
static void *
lxt_door_waitpid(void *arg)
{
int stat;
return (NULL);
}
static char *
{
char *path;
for (;;) {
return (NULL);
return (NULL);
}
/* This file path exists, pick a new name. */
continue;
}
/* We successfully created the fifo */
break;
}
return (path);
}
static void
{
char fifo1_path_native[MAXPATHLEN];
int junk;
lxt_debug("lxt_door_init: preparint to start server");
/* Create two new fifos. */
goto fail;
"/native%s", fifo1_path);
/*
* Open both fifos for reading and writing. We have to open
* the read side of the fifo first (because the write side will
* fail to open if there is no reader) and we have to use the
* O_NONBLOCK flag (because the read open with hang without it).
*/
goto fail;
/*
* Now we have to close the read side of fifo1 and fifo2 and re-open
* them without the O_NONBLOCK flag. This is because we're using
* the fifos for synchronization and when we actually try to read
* from them we want to block.
*/
goto fail;
goto fail;
/*
* Once fifo2 is opened no one will ever need to open it again
* so delete it now.
*/
(void) unlink(fifo2_path);
fifo2_path = NULL;
/* Attempt to fork and start the door server */
lxt_debug("lxt_door_init: starting server");
case -1:
/* fork1() failed. */
goto fail;
case 0:
/* Child process - new door server. */
/* Need to chroot back to the real root directory */
lxt_debug("lxt_server_exec: "
exit(-1);
}
/* Start the server */
lxt_debug("lxt_server_exec: server init failed");
exit(-1);
/*NOTREACHED*/
}
/* Parent process - door client. */
/*
* fifo2 is used to send the door path to the child.
* (We can't simply pass it via the address space since the
* child will need to exec.) We'll write the name of the door
* file to fifo2 before we close the read end of the fifo2 so
* that if the child has exited for some reason we won't get
* a SIGPIPE. Note that we're reusing the name of fifo1 as
* the door path. Also note that we've pre-pended /native
* to /native, but when the thunking server executes it will
* be chroot'ed back to the real root directory.
*/
/*
* Start up a thread that will perfom a waitpid() on the child
* door server process. We do this because if the calling
* application that is using our interfaces is forking it's own
* children and using wait(), then it won't expect to see our
* children. We take advantage of the fact that if there are
* wait() and a waitpid() calls in progress at the same time
* when a child exists, preference will be given to any
* waitpid() calls that are explicity waiting for that child.
* There is of course a window of time where the child could
* exit after we've forked it but before we've called waitpid()
* where another wait() in this process could collect the result.
* There's nothing we can really do to prevent this short of
* stopping all the other threads in this process.
*/
(void) thr_create(NULL, 0,
/*
* fifo1 is used for the child process to signal us that the
* door server is ready to take requests.
*/
/* If there was a door that was open, close it now. */
if (lxt_door_fd >= 0)
(void) close(lxt_door_fd);
/*
* The server should be started up by now and fattach()ed the door
* should get a fd to the door server.
*/
(void) unlink(fifo1_path);
return;
fail:
if (fifo1_path != NULL)
(void) unlink(fifo1_path);
if (fifo2_path != NULL)
(void) unlink(fifo2_path);
if (fifo1_rd != -1)
if (fifo1_wr != -1)
if (fifo2_rd != -1)
if (fifo2_wr != -1)
}
static int
{
int fd;
if (!lock_held)
(void) mutex_lock(&lxt_door_lock);
/* Get a copy of lxt_door_fd */
fd = lxt_door_fd;
if (!lock_held)
(void) mutex_unlock(&lxt_door_lock);
if (fd == -1) {
lxt_debug("lxt_door_call: no door available");
return (-1);
}
lxt_debug("lxt_door_call: call failed");
return (-1);
}
lxt_debug("lxt_door_call: call returned NULL");
return (-1);
}
return (0);
}
static int
{
int rv, ping_success = 0;
/* First just try the door call. */
lxt_debug("lxt_door_request: calling server");
if (lxt_door_call(door_arg, 0) == 0)
return (0);
/* Prepare a door server ping request. */
(void) mutex_lock(&lxt_door_lock);
/* Ping the doors server. */
lxt_debug("lxt_door_request: pinging server");
/*LINTED*/
}
if (!ping_success) {
/* The server is not responding so start up a new one. */
}
(void) mutex_unlock(&lxt_door_lock);
/* Retry the original request */
lxt_debug("lxt_door_request: calling server, retry");
return (0);
return (rv);
}
static struct hostent *
{
int request_size, errno_tmp, i;
lxt_debug("lxt_gethost: request caught");
lxt_debug("lxt_gethost: calloc() failed");
return (NULL);
}
/*LINTED*/
/* Initialize the server request. */
/* Initialize door_call() arguments. */
if (lxt_door_request(&door_arg) != 0) {
lxt_debug("lxt_gethost: door_call() failed");
/* Don't know what caused the error so clear errno. */
errno = 0;
return (NULL);
}
lxt_debug("lxt_gethost: door_call() returned NULL");
/* Don't know what caused the error so clear errno. */
errno = 0;
return (NULL);
}
/*LINTED*/
/*LINTED*/
/* Check if the remote procedure call failed */
if (!request->lxt_sa_success) {
lxt_debug("lxt_gethost: remote function call failed");
return (NULL);
}
/* Copy out the results and output buffer. */
/* Now go through the results and convert all offsets to pointers */
}
result->h_addr_list[i] =
}
return (result);
}
static struct servent *
{
int request_size, errno_tmp, i;
lxt_debug("lxt_getserv: request caught");
lxt_debug("lxt_getserv: calloc() failed");
return (NULL);
}
/*LINTED*/
/* Initialize the server request. */
sizeof (data->lxt_gs_proto));
/* Initialize door_call() arguments. */
/* Call the doors server */
if (lxt_door_request(&door_arg) != 0) {
lxt_debug("lxt_getserv: door_call() failed");
/* Don't know what caused the error so clear errno */
errno = 0;
return (NULL);
}
lxt_debug("lxt_getserv: door_call() returned NULL");
/* Don't know what caused the error so clear errno */
errno = 0;
return (NULL);
}
/*LINTED*/
/*LINTED*/
/* Check if the remote procedure call failed */
if (!request->lxt_sa_success) {
lxt_debug("lxt_getserv: remote function call failed");
return (NULL);
}
/* Copy out the results and output buffer. */
/*
* Now go through the results and convert all offsets to pointers.
* See the comments in lxt_server_getserv() for why we need
* to subtract 1 from each offset.
*/
}
return (result);
}
static void
{
int request_size;
lxt_debug("lxt_openlog: calloc() failed");
return;
}
/*LINTED*/
/* Initialize the server request. */
/* Initialize door_call() arguments. */
/* Call the doors server */
if (lxt_door_request(&door_arg) != 0) {
lxt_debug("lxt_openlog: door_call() failed");
return;
}
lxt_debug("lxt_openlog: door_call() returned NULL");
return;
}
/*LINTED*/
/* Check if the remote procedure call failed */
if (!request->lxt_sa_success) {
lxt_debug("lxt_openlog: remote function call failed");
}
}
static void
{
psinfo_t p;
int errno_backup = errno;
/*
* Here we're going to use vsnprintf() to expand the message
* string passed in before we hand it off to a Linux process.
* Before we can call vsnprintf() we'll need to do modify the
* string to deal with certain special tokens.
*
* syslog() supports a special '%m' format token that expands to
* the error message string associated with the current value
* of errno. Unfortunatly if we pass this token to vsnprintf()
* it will choke so we need to expand that token manually here.
*
* We also need to expand any "%%" characters into "%%%%".
* The reason is that we'll be calling vsnprintf() which will
* translate "%%%%" back to "%%", which is safe to pass to the
* Linux version if syslog. If we didn't do this then vsnprintf()
* would translate "%%" to "%" and then the Linux syslog would
* attempt to intrepret "%" and whatever character follows it
* as a printf format style token.
*/
key = 1;
continue;
}
tok_count++;
err_count++;
key = 0;
}
/* We found some tokens that we need to expand. */
/* Allocate a buffer to hold the expanded string. */
buf_len = i + 1 +
lxt_debug("lxt_vsyslog: calloc() failed");
return;
}
/* Finally, expand %% and %m. */
key = 1;
continue;
}
} else {
}
key = 0;
}
/* Use the expanded buffer as our format string. */
}
/* Allocate the request we're going to send to the server */
lxt_debug("lxt_vsyslog: calloc() failed");
return;
}
/*LINTED*/
/* Initialize the server request. */
/* If we did token expansion then free the intermediate buffer. */
/* Add the current program name into the request */
sizeof (data->lxt_sl_progname));
}
}
/* Initialize door_call() arguments. */
/* Call the doors server */
if (lxt_door_request(&door_arg) != 0) {
lxt_debug("lxt_vsyslog: door_call() failed");
return;
}
lxt_debug("lxt_vsyslog: door_call() returned NULL");
return;
}
/*LINTED*/
/* Check if the remote procedure call failed */
if (!request->lxt_sa_success) {
lxt_debug("lxt_vsyslog: remote function call failed");
}
}
static void
lxt_closelog(void)
{
int request_size;
request_size = sizeof (*request);
lxt_debug("lxt_closelog: calloc() failed");
return;
}
/* Initialize the server request. */
/* Initialize door_call() arguments. */
/* Call the doors server */
if (lxt_door_request(&door_arg) != 0) {
lxt_debug("lxt_closelog: door_call() failed");
return;
}
lxt_debug("lxt_closelog: door_call() returned NULL");
return;
}
/*LINTED*/
/* Check if the remote procedure call failed */
if (!request->lxt_sa_success) {
lxt_debug("lxt_closelog: remote function call failed");
}
}
static void
const char *priv)
{
lxt_debug("lxt_pset_keep: "
"preventing drop of \"%s\" from \"%s\" set",
}
} else {
lxt_debug("lxt_pset_keep: "
"preventing drop of \"%s\" from \"%s\" set",
}
}
}
/*
* Public interfaces - used by lx_nametoaddr
*/
void
{
int rv, n;
if (debug_fd == -1)
return;
return;
/* Format the message. */
return;
/* Add a carrige return if there isn't one already. */
return;
/* We retry in case of EINTR */
do {
}
void
{
int errno_backup;
if (debug_fd == -1)
return;
}
struct hostent *
{
lxt_debug("lxt_gethostbyaddr_r: request recieved");
return (lxt_gethost(LXT_SERVER_OP_ADDR2HOST,
}
struct hostent *
lxt_gethostbyname_r(const char *name,
{
lxt_debug("lxt_gethostbyname_r: request recieved");
return (lxt_gethost(LXT_SERVER_OP_NAME2HOST,
}
struct servent *
{
lxt_debug("lxt_getservbyport_r: request recieved");
return (lxt_getserv(LXT_SERVER_OP_PORT2SERV,
}
struct servent *
{
lxt_debug("lxt_getservbyname_r: request recieved");
return (lxt_getserv(LXT_SERVER_OP_NAME2SERV,
}
/*
* "Public" interfaces - used to override public existing interfaces
*/
int
{
/*
* Don't let the process close our file descriptor that points
* back to the root directory.
*/
return (0);
return (0);
}
int
{
int rv;
lxt_debug("_setppriv: request caught");
(void) sleep(1);
return (rv);
}
void
{
lxt_debug("openlog: request caught");
}
void
{
lxt_debug("syslog: request caught");
}
void
{
lxt_debug("vsyslog: request caught");
}
void
closelog(void)
{
lxt_debug("closelog: request caught");
lxt_closelog();
}