Jexec and access to tty
joris dedieu
joris.dedieu at gmail.com
Thu Aug 11 12:34:03 UTC 2011
2011/8/11 joris dedieu <joris.dedieu at gmail.com>:
> 2011/8/10 joris dedieu <joris.dedieu at gmail.com>:
>> 2011/8/9 Paul Schenkeveld <freebsd at psconsult.nl>:
>>> Hi,
>>>
>>> There have been several threads about this issue, some people have come
>>> up with work arounds but I think that the issue is more fundamental,
>>> that's why I wanted to start this new thread.
>>>
>>> When using jexec to do interactive work inside an existing jail, people
>>> find out that they no longer have access to their tty device. As a
>>> result, programs requiring input of passwords or passphrases behave
>>> unexpectedly in one of several ways.
>>>
>>> Ssh says "Host key verification failed." and refuses to log in to
>>> another system (unless pubkey authentication is user in combination with
>>> an agent of course). Some programs fall back to using stdin/stdout
>>> and echo the password as it is typed (the mysql clients are popular
>>> examples).
>>>
>>> Work-arounds that have been suggested are
>>> 1. Run a sshd inside the jail and log in using ssh
>>> 2. Start tmux inside the jail so you get a new pseudo tty slave inside
>>> the jail. People trying screen find that it won't work unlike tmux.
>>> 3. I tried using 'script -q /dev/null' inside the jail because it is
>>> part op the base system and it doesn't change your terminal type
>>> and interpret keyboard input and screen output. I found out that I
>>> failed when I resized my window :-(
>>
>> An other way is to use chroot(5) to enter the jail.
>> Maybe chroot /jail/root login -f $USER should be acceptable in some situations.
>>
>>>
>>> I don't like 1 on a machine with many jails, especially if some of them
>>> share the same IP address (e.g. sometimes I have to run a mail server on
>>> the same IP adress as a webserver but in a distinct jail).
>>>
>>> 2 is not ideal either because tmux emulates a different terminal on
>>> the inside than the terminal on the outside that it runs on.
>>>
>>> 3 is really a kludge and causes problems when you resize your window.
>>>
>>> I thought that I found a solution by rewriting jexec such that it will
>>> open a pseudo tty and does the passing of data between the jailed pts
>>> and the tty from where jexec was started but that's not going to work as
>>> the pseudo tty most be opened by the child process inside the jail but
>>> the parent outside the jail must have access to the master side of the
>>> pseudo tty.
>>>
>>> So far we are still talking about work-arounds. Why not look at the
>>> root cause. Unfortunately I'm not familiar with kernel sources so if
>>> I'm wrong, please forgive me, I write this with the best intentions.
>>>
>>> The root cause of th problem appears to be that pseudo ttys opened
>>> outside a jail are not visible nor accessible inside a jail, pseudo ttys
>>> created inside a jail are visible and accessible though.
>>
>> As far as I understand, sys/fs/devfs/devfs_vnops.c uses
>> prison_check(9) too see if an item as been build in the same jail or
>> in a child.
>> The tty exists inside the jail but you can't use it (and so you can't
>> escape the jail).
>>
>>>
>>> Would it be conceivable that by using jexec the controlling tty of jexec
>>> magically becomes visible and accessible inside the jail? Preferrable
>>> only until jexec dies.
>>
>> I'm not sure. The only way should be to temporary disable the check
>> with a variable. But it will also brake jail security during jexec
>> excution.
>> Using chroot(2) instead of jail(2) should be an option (but it's non
>> trivial to affect jail context for all other subsystems).
>>
>> I think the only right way is to open a new tty while entering the
>> jail (this is what tmux or a jailed ssh does).
>> But it should be difficult to make things like echo ps | jexec 1 sh works.
>
> I gave this way a try and quickly produced a dirty patch. I did not
> yet investigate on the right way, but I already know that I'm wrong on
> several items so sorry for your eyes :)
>
> Basically this patch introduced -t option that forks jexec inside
> the jail, open a new pts and tries to establish a communication with
> the old one.
> It mostly works has expected but breaks some console inputs like
> control commands, completion and so on.
> Before spending more time trying to produce something acceptable,
> please let me know if it should be the expected feature or sounds
> like a nasty workaround.
>
> --- usr.sbin/jexec/jexec.c.orig 2009-08-03 10:13:06.000000000 +0200
> +++ usr.sbin/jexec/jexec.c 2011-08-10 23:33:44.000000000 +0200
> @@ -37,16 +37,19 @@
>
> #include <err.h>
> #include <errno.h>
> +#include <fcntl.h>
> #include <jail.h>
> #include <limits.h>
> #include <login_cap.h>
> #include <stdio.h>
> #include <stdlib.h>
> #include <string.h>
> +#include <termios.h>
> #include <pwd.h>
> #include <unistd.h>
>
> static void usage(void);
> +static int ntty_exec(const char *file, char *const argv[]);
>
> #define GET_USER_INFO do { \
> pwd = getpwnam(username); \
> @@ -64,6 +67,8 @@
> err(1, "getgrouplist: %s", username); \
> } while (0)
>
> +#define MAXREAD 256
> +
> int
> main(int argc, char *argv[])
> {
> @@ -71,17 +76,17 @@
> login_cap_t *lcap = NULL;
> struct passwd *pwd = NULL;
> gid_t *groups = NULL;
> - int ch, ngroups, uflag, Uflag;
> + int ch, ngroups, uflag, Uflag, tflag;
> long ngroups_max;
> char *username;
>
> - ch = uflag = Uflag = 0;
> + ch = uflag = Uflag = tflag = 0;
> username = NULL;
> ngroups_max = sysconf(_SC_NGROUPS_MAX) + 1;
> if ((groups = malloc(sizeof(gid_t) * ngroups_max)) == NULL)
> err(1, "malloc");
>
> - while ((ch = getopt(argc, argv, "nu:U:")) != -1) {
> + while ((ch = getopt(argc, argv, "nu:U:t")) != -1) {
> switch (ch) {
> case 'n':
> /* Specified name, now unused */
> @@ -94,6 +99,9 @@
> username = optarg;
> Uflag = 1;
> break;
> + case 't':
> + tflag = 1;
> + break;
> default:
> usage();
> }
> @@ -125,8 +133,14 @@
> err(1, "setusercontext");
> login_close(lcap);
> }
> - if (execvp(argv[1], argv + 1) == -1)
> - err(1, "execvp(): %s", argv[1]);
> + if (tflag) {
> + if(ntty_exec(argv[1], argv + 1) == -1)
> + err(1, "ntty_exec(): %s", argv[1]);
> + }
> + else {
> + if (execvp(argv[1], argv + 1) == -1)
> + err(1, "execvp(): %s", argv[1]);
> + }
> exit(0);
> }
>
> @@ -138,3 +152,77 @@
> "usage: jexec [-u username | -U username] jail command ...");
> exit(1);
> }
> +
> +static int
> +ntty_exec(const char *file, char *const argv[])
> +{
> + int pseudoterm, pseudoterm_fd, res;
> + struct fd_set in_fd;
> + struct termios termset;
> + char input[MAXREAD];
> +
> + if ((pseudoterm = posix_openpt(O_RDWR)) == -1)
> + err(1, "posix_openpt");
> + if (grantpt(pseudoterm) != 0)
> + err(1, "grantpt");
> + if (unlockpt(pseudoterm) != 0)
> + err(1, "grantpt");
> + if ((pseudoterm_fd = open(ptsname(pseudoterm), O_RDWR)) == -1)
> + err(1, "open");
> +
> + switch (fork()) {
> + case -1 :
> + err(1, "fork");
> + case 0 :
> + close(pseudoterm);
> + if (tcgetattr(pseudoterm_fd, &termset) == -1)
> + err(1, "tcgetattr");
> + cfmakeraw(&termset);
> + tcsetattr(pseudoterm_fd, TCSANOW, &termset);
> + fclose(stdin);
> + fclose(stdout);
> + fclose(stderr);
> + dup(pseudoterm_fd);
> + dup(pseudoterm_fd);
> + dup(pseudoterm_fd);
> + close(pseudoterm_fd);
> + setsid();
> + ioctl(0, TIOCSCTTY, 1);
> + return execvp(file, argv);
> + default :
> + close(pseudoterm_fd);
> + while(1) {
> + FD_ZERO(&in_fd);
> + FD_SET(0, &in_fd);
> + FD_SET(pseudoterm, &in_fd);
> + if (select(pseudoterm + 1, &in_fd, NULL, NULL,
> NULL) == -1)
> + err(1, "select");
> + if (FD_ISSET(0, &in_fd)) {
> + /* stdin */
> + if ((res = read(0, input, sizeof(input))) > 0 )
> + write(pseudoterm, input, res);
> + else {
> + if (errno == ENOENT)
> + return 0;
> + else
> + err(1, "read");
> + }
> + }
> + if (FD_ISSET(pseudoterm, &in_fd)) {
> + if ((res = read(pseudoterm, input,
> + sizeof(input))) > 0 )
> + /* stdout */
> + write(1, input, res);
> + else {
> + if (errno == ENOENT)
> + return 0;
> + else
> + err(1, "write");
> + }
> + }
> + }
> + }
> +
> + return 0;
> +}
> +
>
> regards
> Joris
Finally I produced a most acceptable patch as almost everything exists
in script(1) sources. I don't experienced any problems using it.
--- usr.sbin/jexec/jexec.c.orig 2009-08-03 10:13:06.000000000 +0200
+++ usr.sbin/jexec/jexec.c 2011-08-11 14:25:11.000000000 +0200
@@ -37,16 +37,20 @@
#include <err.h>
#include <errno.h>
+#include <fcntl.h>
#include <jail.h>
#include <limits.h>
+#include <libutil.h>
#include <login_cap.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <termios.h>
#include <pwd.h>
#include <unistd.h>
static void usage(void);
+static void newpty(void);
#define GET_USER_INFO do { \
pwd = getpwnam(username); \
@@ -71,17 +75,17 @@
login_cap_t *lcap = NULL;
struct passwd *pwd = NULL;
gid_t *groups = NULL;
- int ch, ngroups, uflag, Uflag;
+ int ch, ngroups, uflag, Uflag, tflag;
long ngroups_max;
char *username;
- ch = uflag = Uflag = 0;
+ ch = uflag = Uflag = tflag = 0;
username = NULL;
ngroups_max = sysconf(_SC_NGROUPS_MAX) + 1;
if ((groups = malloc(sizeof(gid_t) * ngroups_max)) == NULL)
err(1, "malloc");
- while ((ch = getopt(argc, argv, "nu:U:")) != -1) {
+ while ((ch = getopt(argc, argv, "nu:U:t")) != -1) {
switch (ch) {
case 'n':
/* Specified name, now unused */
@@ -94,6 +98,9 @@
username = optarg;
Uflag = 1;
break;
+ case 't':
+ tflag = 1;
+ break;
default:
usage();
}
@@ -125,6 +132,8 @@
err(1, "setusercontext");
login_close(lcap);
}
+ if (tflag)
+ newpty();
if (execvp(argv[1], argv + 1) == -1)
err(1, "execvp(): %s", argv[1]);
exit(0);
@@ -135,6 +144,68 @@
{
fprintf(stderr, "%s\n",
- "usage: jexec [-u username | -U username] jail command ...");
+ "usage: jexec [-u username | -U username] [-t] jail
command ...");
exit(1);
}
+
+static void
+newpty(void)
+{
+ int master, slave, child, n, cc;
+ fd_set rfd;
+ struct termios tt, rtt;
+ struct winsize win;
+ char obuf[BUFSIZ];
+ char ibuf[BUFSIZ];
+
+ if (tcgetattr(STDIN_FILENO, &tt) == -1)
+ err(1, "tcgetattr");
+ if (ioctl(STDIN_FILENO, TIOCGWINSZ, &win) == -1)
+ err(1, "ioctl");
+ if (openpty(&master, &slave, NULL, &tt, &win) == -1)
+ err(1, "openpty");
+ rtt = tt;
+ cfmakeraw(&rtt);
+ rtt.c_lflag &= ~ECHO;
+ tcsetattr(STDIN_FILENO, TCSAFLUSH, &rtt);
+
+ child = fork();
+ if (child < 0)
+ err(1, "fork");
+ if (child == 0) {
+ close(master);
+ login_tty(slave);
+ dup2(slave, STDIN_FILENO);
+ dup2(slave, STDOUT_FILENO);
+ dup2(slave, STDERR_FILENO);
+ close(slave);
+ return;
+ }
+ FD_ZERO(&rfd);
+ for (;;) {
+ FD_SET(master, &rfd);
+ FD_SET(STDIN_FILENO, &rfd);
+ n = select(master + 1, &rfd, 0, 0, NULL);
+ if (n < 0 && errno != EINTR)
+ break;
+ if (n > 0 && FD_ISSET(STDIN_FILENO, &rfd)) {
+ cc = read(STDIN_FILENO, ibuf, BUFSIZ);
+ if (cc < 0)
+ break;
+ if (cc == 0)
+ write(master, ibuf, 0);
+ if (cc > 0) {
+ write(master, ibuf, cc);
+ }
+ }
+ if (n > 0 && FD_ISSET(master, &rfd)) {
+ cc = read(master, obuf, sizeof (obuf));
+ if (cc <= 0)
+ break;
+ write(STDOUT_FILENO, obuf, cc);
+ }
+ }
+ tcsetattr(STDIN_FILENO, TCSAFLUSH, &tt);
+ exit(0);
+}
+
>
>>
>> regards
>> Joris
>>
>>>
>>> I understand that this is not trivial but given the number of threads
>>> about this problem, it's a real issue to many people. To me it's worth
>>> some $ or EUR to solve this in a clean way.
>>>
>>> Kind regards,
>>>
>>> Paul Schenkeveld
>>> _______________________________________________
>>> freebsd-jail at freebsd.org mailing list
>>> http://lists.freebsd.org/mailman/listinfo/freebsd-jail
>>> To unsubscribe, send any mail to "freebsd-jail-unsubscribe at freebsd.org"
>>>
>>
>
More information about the freebsd-jail
mailing list