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