When forking the child process, wait until the child has exec'd before continuing in the parent.

Also includes a few other changes for safety, e.g. making the child exit via QuitGS rather than exit() or _exit().

The vfork_and_run() routine is based on code from my port of hush.
This commit is contained in:
Stephen Heumann 2016-01-09 12:46:38 -06:00
parent 494c69cf82
commit 8a3b1e25cd
7 changed files with 196 additions and 33 deletions

View File

@ -20,6 +20,7 @@ TELNETD_SRCS = \
libtelnet/getent.c \
libtelnet/posix_openpt.c \
libtelnet/vasprintf.c \
libtelnet/vfork.and.run.c \
telnetd/global.c \
telnetd/slc.c \
telnetd/state.c \

128
libtelnet/vfork.and.run.c Normal file
View File

@ -0,0 +1,128 @@
/*
* Copyright (c) 2014, 2016 Stephen Heumann
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#ifdef __GNO__
#include <gno/gno.h>
#endif
/* Like vfork, but calls fn(arg) in the child instead of returning.
* This is designed to match the semantics of GNO's fork2 call.
*/
#ifndef __GNO__
pid_t vfork_and_run(void (*fn)(void*) /*NORETURN*/, void *arg) {
/* Use fork rather than vfork because vfork is problematic on OS X. */
pid_t pid = fork();
if (pid == 0) {
fn(arg);
}
return pid;
}
#else
# include <signal.h>
# include <gno/kvm.h>
# include <orca.h>
/* Turn off all ORCA/C stack repair code to avoid corruption. */
# ifdef __ORCAC__
# pragma optimize 72
# endif
# pragma databank 1
void fork_thunk(void (*fn)(void*) /*NORETURN*/, void *arg, long sigmask) {
sigsetmask(sigmask);
fn(arg);
}
# pragma databank 0
const char *forked_child_name = "telnetd pty slave proc";
#define CHILD_STACKSIZE 1024
pid_t vfork_and_run(void (*fn)(void*) /*NORETURN*/, void *arg) {
/* GNO's fork2 call will return immediately and allow the parent and
* child processes to execute concurrently using the same memory
* space. To prevent them stomping on each other, we want to get
* behavior like a traditional vfork() implementation, where the
* parent blocks until the child terminates or execs.
*
* Our approach is to check the process tables to make sure the
* child has actually finished or exec'd. If not, we loop and try again.
* We can't just rely on the fact that the child signaled us, because
* it may still be running in libc's implementation of exec*.
*/
long oldmask;
pid_t pid;
kvmt *kvm_context;
struct pentry *proc_entry;
int done = 0;
/* Isolate child process's environment from parent */
if (environPush() != 0)
return -1;
/* Block all signals for now */
oldmask = sigblock(-1);
pid = fork2(fork_thunk, CHILD_STACKSIZE, 0, forked_child_name,
(sizeof(fn) + sizeof(arg) + sizeof(oldmask) + 1) / 2,
fn, arg, oldmask);
if (pid < 0)
goto ret;
while (!done) {
/* Wait for ~100 ms. If procsend worked, the child could send a
* message with it to end the waiting earlier, but this isn't
* possible in GNO 2.0.6 because procsend is broken. This isn't
* too big an issue, since 100ms isn't very long to wait anyhow. */
procrecvtim(1);
/* Check if the child is really dead or forked by inspecting
* the kernel's process entry for it. */
kvm_context = kvm_open();
if (kvm_context == NULL)
break;
proc_entry = kvmgetproc(kvm_context, pid);
if (proc_entry == NULL
|| (proc_entry->args != NULL
&& strcmp(forked_child_name, proc_entry->args + 8) != 0))
done = 1;
kvm_close(kvm_context);
}
ret:
sigsetmask(oldmask);
environPop();
return pid;
}
#endif

View File

@ -0,0 +1 @@
pid_t vfork_and_run(void (*fn)(void*) /*NORETURN*/, void *arg);

View File

@ -83,6 +83,8 @@ extern int SYNCHing; /* we are in TELNET SYNCH mode */
/* Buffer for miscellaneous uses in various functions */
extern char buf[BUFSIZ > 1024 ? BUFSIZ : 1024];
extern int parent_pid; /* pid of parent (server) process */
extern void
_termstat(void),
add_slc(char, char, cc_t),
@ -166,6 +168,7 @@ extern int
tty_linemode(void);
extern void
safe_exit(int),
tty_rspeed(int),
tty_setecho(int),
tty_setedit(int),
@ -194,7 +197,7 @@ extern char *nclearto;
*/
extern struct {
int
long
system, /* what the current time is */
echotoggle, /* last time user entered echo character */
modenegotiated, /* last time operating mode negotiated */

View File

@ -45,8 +45,13 @@ static const char sccsid[] = "@(#)sys_term.c 8.4+1 (Berkeley) 5/30/95";
#endif
#include <stdlib.h>
#ifdef __GNO__
#include <gsos.h>
#endif
#include "telnetd.h"
#include "pathnames.h"
#include "libtelnet/vfork.and.run.h"
#ifdef AUTHENTICATION
#include "libtelnet/auth.h"
@ -922,22 +927,20 @@ cleanopen(char *li)
return(t);
}
#ifdef __ORCAC__
# pragma databank 1
#endif
struct slaveargs {
char *host;
int autologin;
char *autoname;
};
static void
slaveproc(char *host, int autologin, char *autoname)
slaveproc(void *slaveargs)
{
#ifdef __GNO__
environPush();
#endif
struct slaveargs *args = slaveargs;
getptyslave();
start_login(host, autologin, autoname);
start_login(args->host, args->autologin, args->autoname);
/*NOTREACHED*/
}
#ifdef __ORCAC__
# pragma databank 0
#endif
/*
* startslave(host)
@ -951,6 +954,7 @@ void
startslave(char *host, int autologin, char *autoname)
{
int i;
struct slaveargs slaveargs = {host, autologin, autoname};
#ifdef AUTHENTICATION
if (!autoname || !autoname[0])
@ -962,25 +966,9 @@ startslave(char *host, int autologin, char *autoname)
}
#endif
#ifndef __GNO__
i = fork();
#else
i = fork2(slaveproc, 1024, 0, "telnetd pty slave proc",
(sizeof(host) + sizeof(autologin) + sizeof(autoname))/2,
host, autologin, autoname);
#endif
i = vfork_and_run(slaveproc, &slaveargs);
if (i < 0)
fatalperror(net, "fork");
if (i) {
#ifdef __GNO__
do {
errno = 0;
(void)wait(NULL);
} while (errno == EINTR);
#endif
} else {
slaveproc(host, autologin, autoname);
}
}
void
@ -1276,6 +1264,47 @@ scrub_env(void)
environ = new_environ;
}
#ifdef __GNO__
static QuitRecGS quitRec = {0, NULL, 0};
#endif
/*
* safe_exit()
*
* Exit in a way that's safe for either the forked child or
* the parent.
*/
void
safe_exit(int status)
{
if (getpid() == parent_pid) {
exit(status);
} else {
#ifndef __GNO__
_exit(status);
#else
/* _exit (contrary to its documentation) performs clean-up
* that's inappropriate for a forked child process (this
* usually results in corruption of the memory allocator
* state, and maybe other problems), so we define our own
* function without this problem. We call QuitGS in assembly
* so we can push the return value on the stack.
*/
while (1) {
asm {
lda status
pha
jsl 0xE100A8
dcw 0x2029 /* QuitGS */
dcl quitRec;
pla
}
}
#endif
}
}
/*
* cleanup()
*
@ -1288,5 +1317,5 @@ cleanup(int sig __unused)
{
(void) shutdown(net, SHUT_RDWR);
exit(1);
safe_exit(1);
}

View File

@ -156,13 +156,14 @@ main(int argc, char *argv[])
_reportStack();
/* Make sure our environment is isolated from the parent process's */
if (environPush() == 0)
if (environPush() == 0 && environInit() == 0)
atexit(environPop);
else
exit(1);
environInit();
#endif
parent_pid = getpid();
pfrontp = pbackp = ptyobuf;
netip = netibuf;
nfrontp = nbackp = netobuf;

View File

@ -339,7 +339,7 @@ fatal(int f, const char *msg)
#endif /* ENCRYPTION */
(void) write(f, buf, (int)strlen(buf));
sleep(1); /*XXX*/
exit(1);
safe_exit(1);
}
void