Add regress for noexec functionality
This commit is contained in:
1
MANIFEST
1
MANIFEST
@@ -577,6 +577,7 @@ src/openbsd.c
|
|||||||
src/parse_args.c
|
src/parse_args.c
|
||||||
src/preload.c
|
src/preload.c
|
||||||
src/preserve_fds.c
|
src/preserve_fds.c
|
||||||
|
src/regress/noexec/check_noexec.c
|
||||||
src/regress/ttyname/check_ttyname.c
|
src/regress/ttyname/check_ttyname.c
|
||||||
src/selinux.c
|
src/selinux.c
|
||||||
src/sesh.c
|
src/sesh.c
|
||||||
|
8
configure
vendored
8
configure
vendored
@@ -750,6 +750,7 @@ LDAP
|
|||||||
SELINUX_USAGE
|
SELINUX_USAGE
|
||||||
BSDAUTH_USAGE
|
BSDAUTH_USAGE
|
||||||
DONT_LEAK_PATH_INFO
|
DONT_LEAK_PATH_INFO
|
||||||
|
CHECK_NOEXEC
|
||||||
INSTALL_NOEXEC
|
INSTALL_NOEXEC
|
||||||
INSTALL_BACKUP
|
INSTALL_BACKUP
|
||||||
sesh_file
|
sesh_file
|
||||||
@@ -2855,6 +2856,7 @@ $as_echo "$as_me: Configuring Sudo version $PACKAGE_VERSION" >&6;}
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -2906,6 +2908,7 @@ PLUGINDIR=/usr/local/libexec/sudo
|
|||||||
#
|
#
|
||||||
INSTALL_BACKUP=
|
INSTALL_BACKUP=
|
||||||
INSTALL_NOEXEC=
|
INSTALL_NOEXEC=
|
||||||
|
CHECK_NOEXEC=
|
||||||
exampledir='$(docdir)/examples'
|
exampledir='$(docdir)/examples'
|
||||||
devdir='$(srcdir)'
|
devdir='$(srcdir)'
|
||||||
PROGS="sudo"
|
PROGS="sudo"
|
||||||
@@ -24406,6 +24409,11 @@ if test X"$with_noexec" != X"no" -o X"$with_selinux" != X"no" -o "$enabled_share
|
|||||||
PROGS="${PROGS} sudo_noexec.la"
|
PROGS="${PROGS} sudo_noexec.la"
|
||||||
INSTALL_NOEXEC="install-noexec"
|
INSTALL_NOEXEC="install-noexec"
|
||||||
|
|
||||||
|
# Can't use asan with LD_PRELOAD
|
||||||
|
if test "$enable_asan" != "yes"; then
|
||||||
|
CHECK_NOEXEC=check_sudo_noexec
|
||||||
|
fi
|
||||||
|
|
||||||
noexec_file="$with_noexec"
|
noexec_file="$with_noexec"
|
||||||
_noexec_file=
|
_noexec_file=
|
||||||
while test X"$noexec_file" != X"$_noexec_file"; do
|
while test X"$noexec_file" != X"$_noexec_file"; do
|
||||||
|
@@ -65,6 +65,7 @@ AC_SUBST([noexec_file])
|
|||||||
AC_SUBST([sesh_file])
|
AC_SUBST([sesh_file])
|
||||||
AC_SUBST([INSTALL_BACKUP])
|
AC_SUBST([INSTALL_BACKUP])
|
||||||
AC_SUBST([INSTALL_NOEXEC])
|
AC_SUBST([INSTALL_NOEXEC])
|
||||||
|
AC_SUBST([CHECK_NOEXEC])
|
||||||
AC_SUBST([DONT_LEAK_PATH_INFO])
|
AC_SUBST([DONT_LEAK_PATH_INFO])
|
||||||
AC_SUBST([BSDAUTH_USAGE])
|
AC_SUBST([BSDAUTH_USAGE])
|
||||||
AC_SUBST([SELINUX_USAGE])
|
AC_SUBST([SELINUX_USAGE])
|
||||||
@@ -189,6 +190,7 @@ dnl May be overridden by environment variables..
|
|||||||
dnl
|
dnl
|
||||||
INSTALL_BACKUP=
|
INSTALL_BACKUP=
|
||||||
INSTALL_NOEXEC=
|
INSTALL_NOEXEC=
|
||||||
|
CHECK_NOEXEC=
|
||||||
exampledir='$(docdir)/examples'
|
exampledir='$(docdir)/examples'
|
||||||
devdir='$(srcdir)'
|
devdir='$(srcdir)'
|
||||||
PROGS="sudo"
|
PROGS="sudo"
|
||||||
@@ -4180,6 +4182,11 @@ if test X"$with_noexec" != X"no" -o X"$with_selinux" != X"no" -o "$enabled_share
|
|||||||
PROGS="${PROGS} sudo_noexec.la"
|
PROGS="${PROGS} sudo_noexec.la"
|
||||||
INSTALL_NOEXEC="install-noexec"
|
INSTALL_NOEXEC="install-noexec"
|
||||||
|
|
||||||
|
# Can't use asan with LD_PRELOAD
|
||||||
|
if test "$enable_asan" != "yes"; then
|
||||||
|
CHECK_NOEXEC=check_sudo_noexec
|
||||||
|
fi
|
||||||
|
|
||||||
noexec_file="$with_noexec"
|
noexec_file="$with_noexec"
|
||||||
_noexec_file=
|
_noexec_file=
|
||||||
while test X"$noexec_file" != X"$_noexec_file"; do
|
while test X"$noexec_file" != X"$_noexec_file"; do
|
||||||
|
@@ -459,13 +459,11 @@ sudo_conf_sesh_path_v1(void)
|
|||||||
return sudo_conf_paths.sesh;
|
return sudo_conf_paths.sesh;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _PATH_SUDO_NOEXEC
|
|
||||||
const char *
|
const char *
|
||||||
sudo_conf_noexec_path_v1(void)
|
sudo_conf_noexec_path_v1(void)
|
||||||
{
|
{
|
||||||
return sudo_conf_paths.noexec;
|
return sudo_conf_paths.noexec;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef _PATH_SUDO_PLUGIN_DIR
|
#ifdef _PATH_SUDO_PLUGIN_DIR
|
||||||
const char *
|
const char *
|
||||||
|
@@ -117,6 +117,8 @@ OBJS = conversation.o env_hooks.o exec.o exec_common.o exec_pty.o \
|
|||||||
|
|
||||||
SESH_OBJS = sesh.o exec_common.o
|
SESH_OBJS = sesh.o exec_common.o
|
||||||
|
|
||||||
|
CHECK_NOEXEC_OBJS = check_noexec.o exec_common.o
|
||||||
|
|
||||||
CHECK_TTYNAME_OBJS = check_ttyname.o ttyname.o
|
CHECK_TTYNAME_OBJS = check_ttyname.o ttyname.o
|
||||||
|
|
||||||
LIBOBJDIR = $(top_builddir)/@ac_config_libobj_dir@/
|
LIBOBJDIR = $(top_builddir)/@ac_config_libobj_dir@/
|
||||||
@@ -155,6 +157,9 @@ sudo_noexec.la: libsudo_noexec.la
|
|||||||
sesh: $(SESH_OBJS) $(LT_LIBS)
|
sesh: $(SESH_OBJS) $(LT_LIBS)
|
||||||
$(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(SESH_OBJS) $(LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(LIBS)
|
$(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(SESH_OBJS) $(LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(LIBS)
|
||||||
|
|
||||||
|
check_noexec: $(CHECK_NOEXEC_OBJS) $(top_builddir)/lib/util/libsudo_util.la
|
||||||
|
$(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(CHECK_NOEXEC_OBJS) $(TEST_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(TEST_LIBS)
|
||||||
|
|
||||||
check_ttyname: $(CHECK_TTYNAME_OBJS) $(top_builddir)/lib/util/libsudo_util.la
|
check_ttyname: $(CHECK_TTYNAME_OBJS) $(top_builddir)/lib/util/libsudo_util.la
|
||||||
$(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(CHECK_TTYNAME_OBJS) $(TEST_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(TEST_LIBS)
|
$(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(CHECK_TTYNAME_OBJS) $(TEST_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(TEST_LIBS)
|
||||||
|
|
||||||
@@ -217,14 +222,19 @@ splint:
|
|||||||
cppcheck:
|
cppcheck:
|
||||||
cppcheck $(CPPCHECK_OPTS) -I$(incdir) -I$(top_builddir) -I. -I$(srcdir) -I$(top_srcdir) $(srcdir)/*.c
|
cppcheck $(CPPCHECK_OPTS) -I$(incdir) -I$(top_builddir) -I. -I$(srcdir) -I$(top_srcdir) $(srcdir)/*.c
|
||||||
|
|
||||||
check: $(TEST_PROGS)
|
check: $(TEST_PROGS) @CHECK_NOEXEC@
|
||||||
@if test X"$(cross_compiling)" != X"yes"; then \
|
@if test X"$(cross_compiling)" != X"yes"; then \
|
||||||
./check_ttyname; \
|
./check_ttyname; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
check_sudo_noexec: sudo_noexec.la check_noexec
|
||||||
|
@if test X"$(cross_compiling)" != X"yes"; then \
|
||||||
|
./check_noexec .libs/$(noexecfile); \
|
||||||
|
fi
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
-$(LIBTOOL) $(LTFLAGS) --mode=clean rm -f $(PROGS) $(TEST_PROGS) \
|
-$(LIBTOOL) $(LTFLAGS) --mode=clean rm -f $(PROGS) $(TEST_PROGS) \
|
||||||
*.lo *.o *.la *.a stamp-* core *.core core.*
|
*.lo *.o *.la *.a stamp-* core *.core core.* nohup.out
|
||||||
|
|
||||||
mostlyclean: clean
|
mostlyclean: clean
|
||||||
|
|
||||||
@@ -239,6 +249,11 @@ realclean: distclean
|
|||||||
cleandir: realclean
|
cleandir: realclean
|
||||||
|
|
||||||
# Autogenerated dependencies, do not modify
|
# Autogenerated dependencies, do not modify
|
||||||
|
check_noexec.o: $(srcdir)/regress/noexec/check_noexec.c \
|
||||||
|
$(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
|
||||||
|
$(incdir)/sudo_fatal.h $(incdir)/sudo_util.h \
|
||||||
|
$(top_builddir)/config.h
|
||||||
|
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/regress/noexec/check_noexec.c
|
||||||
check_ttyname.o: $(srcdir)/regress/ttyname/check_ttyname.c \
|
check_ttyname.o: $(srcdir)/regress/ttyname/check_ttyname.c \
|
||||||
$(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
|
$(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
|
||||||
$(incdir)/sudo_debug.h $(incdir)/sudo_fatal.h \
|
$(incdir)/sudo_debug.h $(incdir)/sudo_fatal.h \
|
||||||
|
@@ -151,8 +151,8 @@ preload_dso(char *envp[], const char *dso_file)
|
|||||||
* to run. On systems with privilege sets, we can remove the exec
|
* to run. On systems with privilege sets, we can remove the exec
|
||||||
* privilege. On other systems we use LD_PRELOAD and the like.
|
* privilege. On other systems we use LD_PRELOAD and the like.
|
||||||
*/
|
*/
|
||||||
static char **
|
char **
|
||||||
disable_execute(char *envp[])
|
disable_execute(char *envp[], const char *dso)
|
||||||
{
|
{
|
||||||
debug_decl(disable_execute, SUDO_DEBUG_UTIL)
|
debug_decl(disable_execute, SUDO_DEBUG_UTIL)
|
||||||
|
|
||||||
@@ -167,8 +167,8 @@ disable_execute(char *envp[])
|
|||||||
#endif /* HAVE_PRIV_SET */
|
#endif /* HAVE_PRIV_SET */
|
||||||
|
|
||||||
#ifdef _PATH_SUDO_NOEXEC
|
#ifdef _PATH_SUDO_NOEXEC
|
||||||
if (sudo_conf_noexec_path() != NULL)
|
if (dso != NULL)
|
||||||
envp = preload_dso(envp, sudo_conf_noexec_path());
|
envp = preload_dso(envp, dso);
|
||||||
#endif /* _PATH_SUDO_NOEXEC */
|
#endif /* _PATH_SUDO_NOEXEC */
|
||||||
|
|
||||||
debug_return_ptr(envp);
|
debug_return_ptr(envp);
|
||||||
@@ -187,7 +187,7 @@ sudo_execve(int fd, const char *path, char *const argv[], char *envp[], bool noe
|
|||||||
|
|
||||||
/* Modify the environment as needed to disable further execve(). */
|
/* Modify the environment as needed to disable further execve(). */
|
||||||
if (noexec)
|
if (noexec)
|
||||||
envp = disable_execute(envp);
|
envp = disable_execute(envp, sudo_conf_noexec_path());
|
||||||
|
|
||||||
#ifdef HAVE_FEXECVE
|
#ifdef HAVE_FEXECVE
|
||||||
if (fd != -1)
|
if (fd != -1)
|
||||||
|
197
src/regress/noexec/check_noexec.c
Normal file
197
src/regress/noexec/check_noexec.c
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2016 Todd C. Miller <Todd.Miller@courtesan.com>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <config.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#ifdef HAVE_STDBOOL_H
|
||||||
|
# include <stdbool.h>
|
||||||
|
#else
|
||||||
|
# include "compat/stdbool.h"
|
||||||
|
#endif /* HAVE_STDBOOL_H */
|
||||||
|
#ifdef HAVE_STRING_H
|
||||||
|
# include <string.h>
|
||||||
|
#endif /* HAVE_STRING_H */
|
||||||
|
#ifdef HAVE_STRINGS_H
|
||||||
|
# include <strings.h>
|
||||||
|
#endif /* HAVE_STRINGS_H */
|
||||||
|
#ifdef HAVE_WORDEXP_H
|
||||||
|
# include <wordexp.h>
|
||||||
|
#endif
|
||||||
|
#include <signal.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include "sudo_compat.h"
|
||||||
|
#include "sudo_fatal.h"
|
||||||
|
#include "sudo_util.h"
|
||||||
|
#include "sudo_exec.h"
|
||||||
|
|
||||||
|
__dso_public int main(int argc, char *argv[], char *envp[]);
|
||||||
|
|
||||||
|
static bool
|
||||||
|
report_status(int status, const char *what)
|
||||||
|
{
|
||||||
|
bool ret = false;
|
||||||
|
|
||||||
|
/* system() returns -1 for exec failure. */
|
||||||
|
if (status == -1) {
|
||||||
|
printf("%s: OK (%s)\n", getprogname(), what);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check exit value, expecting 127 for failure */
|
||||||
|
if (WIFEXITED(status)) {
|
||||||
|
int exitval = WEXITSTATUS(status);
|
||||||
|
if (exitval == 127) {
|
||||||
|
printf("%s: OK (%s)\n", getprogname(), what);
|
||||||
|
ret = true;
|
||||||
|
} else {
|
||||||
|
printf("%s: FAIL (%s) [%d]\n", getprogname(), what, exitval);
|
||||||
|
}
|
||||||
|
} else if (WIFSIGNALED(status)) {
|
||||||
|
printf("%s: FAIL (%s) [signal %d]\n", getprogname(), what,
|
||||||
|
WTERMSIG(status));
|
||||||
|
} else {
|
||||||
|
/* should not happen */
|
||||||
|
printf("%s: FAIL (%s) [status %d]\n", getprogname(), what, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
try_execl(void)
|
||||||
|
{
|
||||||
|
pid_t child, pid;
|
||||||
|
int status;
|
||||||
|
|
||||||
|
child = fork();
|
||||||
|
switch (child) {
|
||||||
|
case -1:
|
||||||
|
sudo_fatal_nodebug("fork");
|
||||||
|
case 0:
|
||||||
|
/* child */
|
||||||
|
/* Try to exec /bin/true, else exit with value 127. */
|
||||||
|
execl("/bin/true", "true", (char *)0);
|
||||||
|
_exit(127);
|
||||||
|
default:
|
||||||
|
/* parent */
|
||||||
|
do {
|
||||||
|
pid = waitpid(child, &status, 0);
|
||||||
|
} while (pid == -1 && errno == EINTR);
|
||||||
|
if (pid == -1)
|
||||||
|
sudo_fatal_nodebug("waitpid");
|
||||||
|
|
||||||
|
if (report_status(status, "execl"))
|
||||||
|
return 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
try_system(void)
|
||||||
|
{
|
||||||
|
int status;
|
||||||
|
|
||||||
|
/* Try to run /bin/true, system() returns 127 on exec failure. */
|
||||||
|
status = system("/bin/true > /dev/null 2>&1");
|
||||||
|
|
||||||
|
if (report_status(status, "system"))
|
||||||
|
return 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_WORDEXP_H
|
||||||
|
static int
|
||||||
|
try_wordexp(void)
|
||||||
|
{
|
||||||
|
wordexp_t we;
|
||||||
|
int rc, ret = 1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* sudo_noexec.so prevents command substitution via the WRDE_NOCMD flag
|
||||||
|
* where possible.
|
||||||
|
*/
|
||||||
|
rc = wordexp("$(/bin/echo foo)", &we, 0);
|
||||||
|
switch (rc) {
|
||||||
|
case -1:
|
||||||
|
#ifdef WRDE_ERRNO
|
||||||
|
case WRDE_ERRNO:
|
||||||
|
/*
|
||||||
|
* Solaris returns WRDE_ERRNO for execve() failure and sudo's
|
||||||
|
* wordexp() wrapper returns -1 if RTLD_NEXT is not supported.
|
||||||
|
*/
|
||||||
|
printf("%s: MOSTLY OK (wordexp)\n", getprogname());
|
||||||
|
ret = 0;
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
case WRDE_CMDSUB:
|
||||||
|
printf("%s: OK (wordexp)\n", getprogname());
|
||||||
|
ret = 0;
|
||||||
|
break;
|
||||||
|
case 0:
|
||||||
|
/*
|
||||||
|
* On HP-UX 11.00 we don't seem to be able to add WRDE_NOCMD
|
||||||
|
* but the execve() wrapper prevents the command substitution.
|
||||||
|
*/
|
||||||
|
if (we.we_wordc == 0) {
|
||||||
|
printf("%s: MOSTLY OK (wordexp)\n", getprogname());
|
||||||
|
ret = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
wordfree(&we);
|
||||||
|
/* FALLTHROUGH */
|
||||||
|
default:
|
||||||
|
printf("%s: FAIL (wordexp) [%d]\n", getprogname(), rc);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char *argv[], char *envp[])
|
||||||
|
{
|
||||||
|
int errors = 0;
|
||||||
|
|
||||||
|
initprogname(argc > 0 ? argv[0] : "check_noexec");
|
||||||
|
|
||||||
|
if (argc != 2) {
|
||||||
|
fprintf(stderr, "usage: %s regress | /path/to/sudo_noexec.so\n", getprogname());
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Disable execution for post-exec and re-exec ourself. */
|
||||||
|
if (strcmp(argv[1], "rexec") != 0) {
|
||||||
|
const char *noexec = argv[1];
|
||||||
|
argv[1] = "rexec";
|
||||||
|
execve(argv[0], argv, disable_execute(envp, noexec));
|
||||||
|
sudo_fatalx_nodebug("execve");
|
||||||
|
}
|
||||||
|
|
||||||
|
errors += try_execl();
|
||||||
|
errors += try_system();
|
||||||
|
#ifdef HAVE_WORDEXP_H
|
||||||
|
errors += try_wordexp();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return errors;
|
||||||
|
}
|
@@ -73,11 +73,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/* exec.c */
|
/* exec.c */
|
||||||
struct sudo_event_base;
|
|
||||||
int sudo_execve(int fd, const char *path, char *const argv[], char *envp[], bool noexec);
|
|
||||||
extern volatile pid_t cmnd_pid;
|
extern volatile pid_t cmnd_pid;
|
||||||
|
|
||||||
|
/* exec_common.c */
|
||||||
|
int sudo_execve(int fd, const char *path, char *const argv[], char *envp[], bool noexec);
|
||||||
|
char **disable_execute(char *envp[], const char *dso);
|
||||||
|
|
||||||
/* exec_pty.c */
|
/* exec_pty.c */
|
||||||
|
struct sudo_event_base;
|
||||||
struct command_details;
|
struct command_details;
|
||||||
struct command_status;
|
struct command_status;
|
||||||
int fork_pty(struct command_details *details, int sv[], sigset_t *omask);
|
int fork_pty(struct command_details *details, int sv[], sigset_t *omask);
|
||||||
|
Reference in New Issue
Block a user