Date: Thu, 11 Aug 2011 14:34:01 +0200 From: joris dedieu <joris.dedieu@gmail.com> To: freebsd-jail@freebsd.org Subject: Re: Jexec and access to tty Message-ID: <CAPd55qC1k9SP=k1QO%2B_iSLwkBj0QWRrnqPiVzweZtwBW=UjbEw@mail.gmail.com> In-Reply-To: <CAPd55qDCpF6%2BNJTKCNUZKEVp3JRnrF8VLKUgFVtLc7fxQODA=w@mail.gmail.com> References: <20110809124543.GA22077@psconsult.nl> <CAPd55qBGAkQDxQ6cy7=M%2BxxX%2BHnAoR%2B8_iCS6QMxxSi-iNnniA@mail.gmail.com> <CAPd55qDCpF6%2BNJTKCNUZKEVp3JRnrF8VLKUgFVtLc7fxQODA=w@mail.gmail.com>
next in thread | previous in thread | raw e-mail | index | archive | help
2011/8/11 joris dedieu <joris.dedieu@gmail.com>: > 2011/8/10 joris dedieu <joris.dedieu@gmail.com>: >> 2011/8/9 Paul Schenkeveld <freebsd@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. =A0As 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 wit= h >>> an agent of course). =A0Some 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 >>> =A01. Run a sshd inside the jail and log in using ssh >>> =A02. Start tmux inside the jail so you get a new pseudo tty slave insi= de >>> =A0 =A0the jail. =A0People trying screen find that it won't work unlike= tmux. >>> =A03. I tried using 'script -q /dev/null' inside the jail because it is >>> =A0 =A0part op the base system and it doesn't change your terminal type >>> =A0 =A0and interpret keyboard input and screen output. =A0I found out t= hat I >>> =A0 =A0failed 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 situ= ations. >> >>> >>> 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 o= n >>> 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 a= s >>> 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. =A0Why not look at the >>> root cause. =A0Unfortunately 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 tty= s >>> 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 jexe= c >>> magically becomes visible and accessible inside the jail? =A0Preferrabl= e >>> only until jexec dies. >> >> I'm not sure. The only way should be =A0to temporary disable the check >> with a =A0variable. But it will also brake jail security during jexec >> excution. >> Using chroot(2) instead of jail(2) should be an option (but it's =A0non >> 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 work= s. > > 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 =A0introduced =A0-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 =A0trying to produce something acceptable, > please let =A0me 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 =A0 =A0 =A02011-08-10 23:33:44.000000000 +0200 > @@ -37,16 +37,19 @@ > > =A0#include <err.h> > =A0#include <errno.h> > +#include <fcntl.h> > =A0#include <jail.h> > =A0#include <limits.h> > =A0#include <login_cap.h> > =A0#include <stdio.h> > =A0#include <stdlib.h> > =A0#include <string.h> > +#include <termios.h> > =A0#include <pwd.h> > =A0#include <unistd.h> > > =A0static void =A0 =A0usage(void); > +static int =A0 =A0 ntty_exec(const char *file, char *const argv[]); > > =A0#define GET_USER_INFO do { =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0= =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 \ > =A0 =A0 =A0 =A0pwd =3D getpwnam(username); =A0 =A0 =A0 =A0 =A0 =A0 =A0 = =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 \ > @@ -64,6 +67,8 @@ > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0err(1, "getgrouplist: %s", username); =A0 = =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 \ > =A0} while (0) > > +#define MAXREAD 256 > + > =A0int > =A0main(int argc, char *argv[]) > =A0{ > @@ -71,17 +76,17 @@ > =A0 =A0 =A0 =A0login_cap_t *lcap =3D NULL; > =A0 =A0 =A0 =A0struct passwd *pwd =3D NULL; > =A0 =A0 =A0 =A0gid_t *groups =3D NULL; > - =A0 =A0 =A0 int ch, ngroups, uflag, Uflag; > + =A0 =A0 =A0 int ch, ngroups, uflag, Uflag, tflag; > =A0 =A0 =A0 =A0long ngroups_max; > =A0 =A0 =A0 =A0char *username; > > - =A0 =A0 =A0 ch =3D uflag =3D Uflag =3D 0; > + =A0 =A0 =A0 ch =3D uflag =3D Uflag =3D tflag =3D 0; > =A0 =A0 =A0 =A0username =3D NULL; > =A0 =A0 =A0 =A0ngroups_max =3D sysconf(_SC_NGROUPS_MAX) + 1; > =A0 =A0 =A0 =A0if ((groups =3D malloc(sizeof(gid_t) * ngroups_max)) =3D= =3D NULL) > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0err(1, "malloc"); > > - =A0 =A0 =A0 while ((ch =3D getopt(argc, argv, "nu:U:")) !=3D -1) { > + =A0 =A0 =A0 while ((ch =3D getopt(argc, argv, "nu:U:t")) !=3D -1) { > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0switch (ch) { > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0case 'n': > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0/* Specified name, now unu= sed */ > @@ -94,6 +99,9 @@ > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0username =3D optarg; > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0Uflag =3D 1; > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0break; > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 case 't': > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 tflag =3D 1; > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 break; > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0default: > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0usage(); > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0} > @@ -125,8 +133,14 @@ > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0err(1, "setusercontext"); > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0login_close(lcap); > =A0 =A0 =A0 =A0} > - =A0 =A0 =A0 if (execvp(argv[1], argv + 1) =3D=3D -1) > - =A0 =A0 =A0 =A0 =A0 =A0 =A0 err(1, "execvp(): %s", argv[1]); > + =A0 =A0 =A0 if (tflag) { > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 if(ntty_exec(argv[1], argv + 1) =3D=3D -1) > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 err(1, "ntty_exec(): %s", a= rgv[1]); > + =A0 =A0 =A0 } > + =A0 =A0 =A0 else { > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 if (execvp(argv[1], argv + 1) =3D=3D -1) > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 err(1, "execvp(): %s", argv= [1]); > + =A0 =A0 =A0 } > =A0 =A0 =A0 =A0exit(0); > =A0} > > @@ -138,3 +152,77 @@ > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0"usage: jexec [-u username | -U username] = jail command ..."); > =A0 =A0 =A0 =A0exit(1); > =A0} > + > +static int > +ntty_exec(const char *file, char *const argv[]) > +{ > + =A0 =A0 =A0 int pseudoterm, pseudoterm_fd, res; > + =A0 =A0 =A0 struct fd_set in_fd; > + =A0 =A0 =A0 struct termios termset; > + =A0 =A0 =A0 char input[MAXREAD]; > + > + =A0 =A0 =A0 if ((pseudoterm =3D posix_openpt(O_RDWR)) =3D=3D -1) > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 err(1, "posix_openpt"); > + =A0 =A0 =A0 if (grantpt(pseudoterm) !=3D 0) > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 err(1, "grantpt"); > + =A0 =A0 =A0 if (unlockpt(pseudoterm) !=3D 0) > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 err(1, "grantpt"); > + =A0 =A0 =A0 if ((pseudoterm_fd =3D open(ptsname(pseudoterm), O_RDWR)) = =3D=3D -1) > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 err(1, "open"); > + > + =A0 =A0 =A0 switch (fork()) { > + =A0 =A0 =A0 case -1 : > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 err(1, "fork"); > + =A0 =A0 =A0 case 0 =A0: > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 close(pseudoterm); > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 if (tcgetattr(pseudoterm_fd, &termset) =3D= =3D -1) > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 err(1, "tcgetattr"); > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 cfmakeraw(&termset); > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 tcsetattr(pseudoterm_fd, TCSANOW, &termset)= ; > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 fclose(stdin); > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 fclose(stdout); > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 fclose(stderr); > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 dup(pseudoterm_fd); > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 dup(pseudoterm_fd); > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 dup(pseudoterm_fd); > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 close(pseudoterm_fd); > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 setsid(); > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 ioctl(0, TIOCSCTTY, 1); > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 return execvp(file, argv); > + =A0 =A0 =A0 default : > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 close(pseudoterm_fd); > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 while(1) { > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 FD_ZERO(&in_fd); > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 FD_SET(0, &in_fd); > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 FD_SET(pseudoterm, &in_fd); > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 if (select(pseudoterm + 1, = &in_fd, NULL, NULL, > NULL) =3D=3D -1) > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 err(1, "sel= ect"); > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 if (FD_ISSET(0, &in_fd)) { > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 /* stdin */ > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 if ((res = =3D read(0, input, sizeof(input))) > 0 ) > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0= =A0 write(pseudoterm, input, res); > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 else { > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0= =A0 if (errno =3D=3D ENOENT) > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0= =A0 =A0 =A0 =A0 =A0 return 0; > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0= =A0 else > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0= =A0 =A0 =A0 =A0 =A0 err(1, "read"); > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 } > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 } > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 if (FD_ISSET(pseudoterm, &i= n_fd)) { > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 if ((res = =3D read(pseudoterm, input, > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 siz= eof(input))) > 0 ) > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0= =A0 /* stdout */ > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0= =A0 write(1, input, res); > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 else { > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0= =A0 if (errno =3D=3D ENOENT) > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0= =A0 =A0 =A0 =A0 =A0 return 0; > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0= =A0 else > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0= =A0 =A0 =A0 =A0 =A0 err(1, "write"); > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0= =A0 } > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 } > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 } > + =A0 =A0 =A0 } > + > + =A0 =A0 =A0 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 =3D getpwnam(username); \ @@ -71,17 +75,17 @@ login_cap_t *lcap =3D NULL; struct passwd *pwd =3D NULL; gid_t *groups =3D NULL; - int ch, ngroups, uflag, Uflag; + int ch, ngroups, uflag, Uflag, tflag; long ngroups_max; char *username; - ch =3D uflag =3D Uflag =3D 0; + ch =3D uflag =3D Uflag =3D tflag =3D 0; username =3D NULL; ngroups_max =3D sysconf(_SC_NGROUPS_MAX) + 1; if ((groups =3D malloc(sizeof(gid_t) * ngroups_max)) =3D=3D NULL) err(1, "malloc"); - while ((ch =3D getopt(argc, argv, "nu:U:")) !=3D -1) { + while ((ch =3D getopt(argc, argv, "nu:U:t")) !=3D -1) { switch (ch) { case 'n': /* Specified name, now unused */ @@ -94,6 +98,9 @@ username =3D optarg; Uflag =3D 1; break; + case 't': + tflag =3D 1; + break; default: usage(); } @@ -125,6 +132,8 @@ err(1, "setusercontext"); login_close(lcap); } + if (tflag) + newpty(); if (execvp(argv[1], argv + 1) =3D=3D -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) =3D=3D -1) + err(1, "tcgetattr"); + if (ioctl(STDIN_FILENO, TIOCGWINSZ, &win) =3D=3D -1) + err(1, "ioctl"); + if (openpty(&master, &slave, NULL, &tt, &win) =3D=3D -1) + err(1, "openpty"); + rtt =3D tt; + cfmakeraw(&rtt); + rtt.c_lflag &=3D ~ECHO; + tcsetattr(STDIN_FILENO, TCSAFLUSH, &rtt); + + child =3D fork(); + if (child < 0) + err(1, "fork"); + if (child =3D=3D 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 =3D select(master + 1, &rfd, 0, 0, NULL); + if (n < 0 && errno !=3D EINTR) + break; + if (n > 0 && FD_ISSET(STDIN_FILENO, &rfd)) { + cc =3D read(STDIN_FILENO, ibuf, BUFSIZ); + if (cc < 0) + break; + if (cc =3D=3D 0) + write(master, ibuf, 0); + if (cc > 0) { + write(master, ibuf, cc); + } + } + if (n > 0 && FD_ISSET(master, &rfd)) { + cc =3D read(master, obuf, sizeof (obuf)); + if (cc <=3D 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. =A0To me it's wor= th >>> some $ or EUR to solve this in a clean way. >>> >>> Kind regards, >>> >>> Paul Schenkeveld >>> _______________________________________________ >>> freebsd-jail@freebsd.org mailing list >>> http://lists.freebsd.org/mailman/listinfo/freebsd-jail >>> To unsubscribe, send any mail to "freebsd-jail-unsubscribe@freebsd.org" >>> >> >
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?CAPd55qC1k9SP=k1QO%2B_iSLwkBj0QWRrnqPiVzweZtwBW=UjbEw>