Building a chroot environment
for sftp and sshd (OpenSSH) on Solaris 8

Kent Cowgill ( kent@c2group.net )


I needed to create a secure environment on solaris where the requirements called for no user having access to anything in the filesystem, or anything from any other user - in fact, not even appearing on the same filesystem. Obviously, a chrooted environment would work best, but OpenSSH's sshd did not support that. Looking all around the 'net, I was unable to find specific instructions for having OpenSSH support chroot for Solaris. Therefore, I found what information I could from other Linux HOWTO's, changed it a bit, and set it up for myself.

These instructions are a bit generalized, and are probably already out of date. OpenSSH, OpenSSL, and GNU gcc probably already have updated versions of their code and/or binaries, but I believe this is a good starting point for anyone else facing the same (or similar) requirements.

First and foremost, we need to ensure that we have at the very least some important versions of the files in question: OpenSSH 3.4p1, OpenSSL 0.9.6g[1], etc.

I have written this HOWTO from a standpoint that, because I'm going to have to do it again, I'd like to have as much of it automated as possible. Therefore, I have every step listed with not only what it does, but in a convenient cut-and-paste format so I could paste the commands directly into my nearest terminal emulator and save my fingers some work. I would normally put everything into one mammoth script, but I like to make absolutely certain I don't miss any error messages. Therefore, I have the steps separated logically into sections that are related.

Disclaimer: Please carefully read all instructions fully before proceeding. If you don't understand what these instructions do, you may run into problems. Be advised,when you follow these instructions, you do so at your own risk. I cannot and will not be held liable for any damages to your system.

  1. Several versions of OpenSSH's sshd between 2.3.1 and 3.3 contain an input validation error that can result in an integer overflow and privilege escalation. All versions between 2.3.1 and 3.3 contain a bug in the PAMAuthenticationViaKbdInt code. All versions between 2.9.9 and 3.3 contain a bug in the ChallengeResponseAuthentication code. OpenSSH 3.4 and later are not affected[2]. Therefore, you need to make sure you have the latest version of OpenSSH as well as the latest versions of the supporting libraries, and especially the compiler. Uninstall older versions of any of the following packages:

    gcc
    zlib
    libgcc
    openssl
    prngd
    
    i.e.:
    pkgrm `pkginfo | grep gcc | awk '{print $2}'`
    pkgrm `pkginfo | grep zlib | awk '{print $2}'`
    pkgrm `pkginfo | grep libgcc | awk '{print $2}'`
    pkgrm `pkginfo | grep ssl | awk '{print $2}'`
    pkgrm `pkginfo | grep ssh | awk '{print $2}'`
    pkgrm `pkginfo | grep prngd | awk '{print $2}'`

  2. Get and install the following packages (available from sunfreeware.com and mirrors):

    gcc-3.2-sol8-sparc-local
    (ftp://ftp.sunfreeware.com/pub/freeware/sparc/8/gcc-3.2-sol8-sparc-local.gz)
    zlib-1.1.4-sol8-sparc-local
    (ftp://ftp.sunfreeware.com/pub/freeware/sparc/8/zlib-1.1.4-sol8-sparc-local)
    libgcc-3.2-sol8-sparc-local
    (ftp://ftp.sunfreeware.com/pub/freeware/sparc/8/libgcc-3.2-sol8-sparc-local)
    openssl-0.9.6g-sol8-sparc-local
    (ftp://ftp.sunfreeware.com/pub/freeware/sparc/8/openssl-0.9.6g-sol8-sparc-local)
    zlib-1.1.4-sol8-sparc-local
    (ftp://ftp.sunfreeware.com/pub/freeware/sparc/8/zlib-1.1.4-sol8-sparc-local)
    prngd-0.9.23-sol8-sparc-local
    (ftp://ftp.sunfreeware.com/pub/freeware/sparc/8/prngd-0.9.23-sol8-sparc-local)
    
    i.e.:
    ( while true;do echo all; echo y; done) | /usr/sbin/pkgadd -d \
    gcc-3.2-sol8-sparc-local > /dev/null 2>&1; break
    ( while true;do echo all; echo y; done) | /usr/sbin/pkgadd -d \
    zlib-1.1.4-sol8-sparc-local > /dev/null 2>&1; break
    ( while true;do echo all; echo y; done) | /usr/sbin/pkgadd -d \
    libgcc-3.2-sol8-sparc-local > /dev/null 2>&1; break
    ( while true;do echo all; echo y; done) | /usr/sbin/pkgadd -d \
    openssl-0.9.6g-sol8-sparc-local > /dev/null 2>&1; break
    ( while true;do echo all; echo y; done) | /usr/sbin/pkgadd -d \
    zlib-1.1.4-sol8-sparc-local > /dev/null 2>&1; break
    ( while true;do echo all; echo y; done) | /usr/sbin/pkgadd -d \
    prngd-0.9.23-sol8-sparc-local > /dev/null 2>&1; break

  3. You will need a way to generate random (or at least pseudo-random) numbers. I have chosen to use prngd, and the rest of these instructions assume that[3]. Therefore, setup and run prngd:

    echo "setting up PRNGD startup script..."
    echo "#!/bin/sh" > /etc/init.d/prngd
    echo "pid=\`/usr/bin/ps -e | /usr/bin/grep prngd | /usr/bin/sed \
      -e 's/^  *//' -e 's/ .*//'`" >> /etc/init.d/prngd
    echo "case \$1 in" >> /etc/init.d/prngd
    echo "'start')" >> /etc/init.d/prngd
    echo "  /usr/local/bin/prngd /var/spool/prngd/pool;;" >> /etc/init.d/prngd
    echo "'stop')" >> /etc/init.d/prngd
    echo "  if [ \"\${pid}\" != \"\" ]; then" >> /etc/init.d/prngd
    echo "  /usr/bin/kill \${pid}; fi;;" >> /etc/init.d/prngd
    echo "*)" >> /etc/init.d/prngd
    echo "  echo \"usage: /etc/init.d/prngd (start|stop)\";;" >> \
    /etc/init.d/prngd
    echo "esac" >> /etc/init.d/prngd
    
    echo "adjusting permissions..."
    chown root:sys /etc/init.d/prngd
    chmod 555 /etc/init.d/prngd
    ln -s /etc/init.d/prngd /etc/rc2.d/S98prngd
    ln -s /etc/init.d/prngd /etc/rc0.d/K02prngd
    
    echo "generating entropy..."
    cat /var/log/syslog /var/adm/messages > /usr/local/etc/prngd/prngd-seed
    mkdir /var/spool/prngd
    /etc/rc2.d/S98prngd start

  4. Get gunzip, and untar the source code tarball (available from http://www.openssh.com/portable.html ) :

    openssh-3.4p1.tar.gz
    (ftp://ftp.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-3.4p1.tar.gz)

  5. Apply the following patch (copy & paste to file: openssh-chroot-3.4p1.patch or click here )[4]:

    --CUT-HERE--
    *** ./openssh-3.4p1/session.c   Wed Jun 26 06:51:06 2002
    --- ./openssh-3.4p1-chroot/session.c    Mon Jul  8 10:35:44 2002
    ***************
    *** 64,69 ****
    --- 64,71 ----
      #define is_winnt       (GetVersion() < 0x80000000)
      #endif
    
    + #define CHROOT
    +
      /* func */
    
      Session *session_new(void);
    ***************
    *** 1159,1164 ****
    --- 1161,1171 ----
      void
      do_setusercontext(struct passwd *pw)
      {
    + #ifdef CHROOT
    +       char *user_dir;
    +       char *new_root;
    + #endif /* CHROOT */
    +
            char tty='\0';
    
      #ifdef HAVE_CYGWIN
    ***************
    *** 1187,1192 ****
    --- 1194,1220 ----
    
                    if (setlogin(pw->pw_name) < 0)
                            error("setlogin failed: %s", strerror(errno));
    +
    +               #ifdef CHROOT
    +                       user_dir = xstrdup(pw->pw_dir);
    +                       new_root = user_dir + 1;
    +
    +                       while((new_root = strchr(new_root, '.')) != NULL) {
    +                               new_root--;
    +                               if(strncmp(new_root, "/./", 3) == 0) {
    +                                       *new_root = '\0';
    +                                       new_root += 2;
    +
    +                                       if(chroot(user_dir) != 0)
    +                                               fatal("Couldn't chroot to user directory %s", user_dir);
    +
    +                                       pw->pw_dir = new_root;
    +                                       break;
    +                               }
    +                               new_root += 2;
    +                       }
    +               #endif /* CHROOT */
    +
                    if (setgid(pw->pw_gid) < 0) {
                            perror("setgid");
                            exit(1);
    --CUT-HERE--

  6. Ensure your PATH is correct for compiling (you'll need to have at the very least gcc, ar, and as in your PATH):

    export PATH=$PATH:/usr/local/bin:/usr/ccs/bin
    

  7. Run configure for openssh[5].
    Using prngd:

    ./configure --with-prngd-socket=/var/spool/prngd \
      --prefix=/usr/local --libexecdir=/usr/libexec/openssh \
      --sysconfdir=/usr/local/etc  --mandir=/usr/share/man
    

    Using /dev/random:

    ./configure --with-random=/dev/random \
      --prefix=/usr/local --libexecdir=/usr/libexec/openssh \
      --sysconfdir=/usr/local/etc --mandir=/usr/share/man
    

  8. Build openssh[6]:

    make

  9. Ensure you have everything setup correctly for privilege separation:

    echo "creating directory..."
    if [[ ! -d /var/empty ]]
      then
        /usr/bin/mkdir /var/empty
        /usr/bin/chown root:sys /var/empty
        /usr/bin/chmod 755 /var/empty
      fi
    
    echo "creating ssh user/group..."
    if [[ `grep sshd /etc/group` == "" ]]
      then
        /usr/sbin/groupadd sshd
      fi
    
    if [[ `grep sshd /etc/passwd` == "" ]]
      then
        /usr/sbin/useradd -g sshd -c 'sshd privsep' -d /var/empty -s /bin/false sshd
      fi
    

  10. Install the compiled openssh:

    make install

  11. Generate keys (if you haven't already done so with a previous version of openssh):

    echo "generating keys..."
    if [[ ! -e /usr/local/etc/ssh_host_key ]]; then /usr/local/bin/ssh-keygen -t rsa1 \
      -f /usr/local/etc/ssh_host_key -N ""; fi
    if [[ ! -e /usr/local/etc/ssh_host_dsa_key  ]]; then /usr/local/bin/ssh-keygen \
      -t dsa -f /usr/local/etc/ssh_host_dsa_key -N ""; fi
    if [[ ! -e /usr/local/etc/ssh_host_rsa_key ]]; then /usr/local/bin/ssh-keygen \
      -t rsa -f /usr/local/etc/ssh_host_rsa_key -N ""; fi

  12. Setup startup script for sshd and use it to start sshd:

    echo "setting up SSH startup script..."
    echo "#!/bin/sh" > /etc/init.d/sshd
    echo "pid=\`/usr/bin/ps -e | /usr/bin/grep sshd | \
      /usr/bin/sed -e 's/^  *//' -e 's/ .*//'\`" >> /etc/init.d/sshd
    echo "case \$1 in" >> /etc/init.d/sshd
    echo "'start')" >> /etc/init.d/sshd
    echo "  /usr/local/sbin/sshd;;" >> /etc/init.d/sshd
    echo "'stop')" >> /etc/init.d/sshd
    echo "  if [ \"\${pid}\" != \"\" ]; then" >> /etc/init.d/sshd
    echo "  /usr/bin/kill \${pid}; fi;;" >> /etc/init.d/sshd
    echo "*)" >> /etc/init.d/sshd
    echo "  echo \"usage: /etc/init.d/sshd (start|stop)\";;" >> /etc/init.d/sshd
    echo "esac" >> /etc/init.d/sshd
    
    echo "adjusting permissions..."
    chown root:sys /etc/init.d/sshd
    chmod 555 /etc/init.d/sshd
    ln -s /etc/init.d/sshd /etc/rc2.d/S98sshd
    ln -s /etc/init.d/sshd /etc/rc0.d/K02sshd
    
    echo "starting sshd..."
    /etc/rc2.d/S98sshd start

  13. You now need to define your jail directory, for example (download a handy ksh script here):

    JAILUSER=jailusername
    JAILGROUP=jailusers
    mkdir /export/home/jail
    /usr/sbin/groupadd $JAILGROUP
    chown root:$JAILGROUP /export/home/jail
    chmod 750 /export/home/jail

    I take the extra step per my specific requirements and create a new jail for every separate user who needs it, but you can alternatively just have proper permissions inside your single jail[7].

    /usr/sbin/useradd -g $JAILGROUP -c "Jail user $JAILUSER" \
      -d /export/home/jail/$JAILUSER/./home/$JAILUSER -s /bin/sh $JAILUSER
    
    mkdir /export/home/jail/$JAILUSER
    chown $JAILUSER:$JAILGROUP /export/home/jail/$JAILUSER

  14. You need the following directory structure inside your jail[8].

    cd /export/home/jail/$JAILUSER
    mkdir etc
    mkdir bin
    mkdir usr
    mkdir usr/bin
    mkdir usr/local
    mkdir usr/local/bin
    mkdir usr/local/libexec
    mkdir usr/local/sbin
    mkdir usr/local/lib
    mkdir usr/local/ssl
    mkdir usr/local/ssl/lib
    mkdir usr/lib
    mkdir usr/platform
    mkdir usr/platform/`uname -i`
    mkdir usr/platform/`uname -i`/lib
    mkdir dev
    mkdir devices
    mkdir devices/pseudo
    mkdir home

  15. Copy most of the various binaries and libraries into your jail[9]:

    cd /export/home/jail/$JAILUSER
    APPS='bin/cp bin/ls bin/mkdir bin/mv bin/pwd bin/rm bin/rmdir bin/sh'
    for i in $APPS; do
      cp /$i ./$i
      LIBS=`ldd ./$i | awk '{print $3}'`
      for l in $LIBS; do
        mkdir ./`dirname $l` > /dev/null
        cp $l .$l
      done
    done

  16. Create pseudo devices and links for dev/null and dev/zero[10]:

    cd /export/home/jail/$JAILUSER/devices/pseudo
    mknod mm@0:zero c 13 12
    mknod mm@0:null c 13 2
    cd /export/home/jail/$JAILUSER/dev
    ln -s ../devices/psuedo/mm@0:zero zero
    ln -s ../devices/pseudo/mm@0:null null

  17. Copy the other binaries and libraries you'll be needing (ldd on solaris doesn't quite pickup all of these)[11] (be sure to scroll all the way to the right of this page; the <PRE> HTML tags don't let this scroll):

    cd /export/home/jail/$JAILUSER
    BINS="usr/local/bin/ssh usr/local/libexec/sftp-server usr/local/sbin/sshd usr/local/lib/libz.so usr/local/ssl/lib/libcrypto.so.0.9.6 usr/lib/ld.so.1 usr/platform/`uname -i`/lib/libc_psr.so.1 usr/lib/nss_files.so.1"
    for i in $BINS; do
      cp /$i ./$i
    done

  18. Create the users' home directory inside the jail:

    mkdir /export/home/jail/$JAILUSER/home/$JAILUSER
    chown $JAILUSER:$JAILGROUP /export/home/jail/$JAILUSER/home/$JAILUSER

  19. Create empty passwd and group files inside your jail[12]:

    touch /export/home/jail/$JAILUSER/etc/passwd
    touch /export/home/jail/$JAILUSER/etc/group

  20. Test your jail by typing (as root)[13]:

    chroot /export/home/jail/jailuser /bin/sh

  21. Test your jail by attempting sftp and/or ssh from another host:

    ssh jailuser@jailhost

 

Resources:

 

Notes:

  1. I have noticed that OpenSSL is (as of the time of this writing) at version 0.9.6g. These instructions are correct for version 0.9.6e. I will update this when I confirm that everything still works for the newer OpenSSL codebase.

  2. Top     <- Back

  3. The preceding text is entirely lifted from http://www.openssh.com/txt/preauth.adv.

  4. Top     <- Back

  5. Alternatively (and perhaps more recommended), install the /dev/random solaris patch (112438-01), reboot, and continue with step 4. In situations where rebooting is not possible, there are some instructions that may work for you that do not require a reboot: http://www.sunmanagers.org/pipermail/summaries/2002-April/002956.html.

    Thanks to Eileen Coles for reminding me about the corresponding kill scripts.

  6. Top     <- Back

  7. Some variation of

    patch < openssh-chroot-3.4p1.patch
    should work, but my patch complained about the file format. I attributed the error to perhaps not using a GNU version of patch when I got the patch file from a linux HOWTO. I saw that there wasn't much code to patch, so I typed it in manually.

  8. Top     <- Back

  9. On my test system (relatively new Sun Netra X1), this took long enough for me to walk across the street for a cup of coffee. YMMV.

  10. Top     <- Back

  11. I ran into a large number of problems here when I originally attempted to compile the code. Parts of the compatibility code (and perhaps more, I couldn't get past that) will not compile with up to gcc 3.0.2, the compiler I originally had on this system. The most fatal errors were 'unknown opcode .previous' and 'unknown opcode .subsection'. Upgrading to gcc 3.2 took care of these issues, but still left a number of (apparently harmless) 'warning: changing search order for system directory "/usr/local/include"' and 'inet_aton.c:155: warning: subscript has type 'char''. By the time I upgraded my compiler, I was content to let these warnings go.

  12. Top     <- Back

  13. The rest of these instructions are specific to my situation with a separate jail for each user. These steps are easily modified to accommodate a single jail.

  14. Top     <- Back

  15. Perhaps obviously, the output of the `uname -i` directory will vary depending on your architecture.

  16. Top     <- Back

  17. Thanks to Alex Kramarov and Gabriele Facciolo for the majority of this script.

  18. Top     <- Back

  19. Thanks to james@firstaid.com for pointing out that these device files were necessary, though I made these a bit more solaris-ish in linking to ../devices/pseudo/etc.

  20. Top     <- Back

  21. Again, inspired in large part by Alex Kramarov and Gabriele Facciolo.

  22. Top     <- Back

  23. My installation performed acceptably with empty password and group files. If you run into problems, try:

  24. echo "$JAILUSER:x:`/usr/xpg4/bin/id -u $JAILUSER`:`/usr/xpg4/bin/id -g $JAILGROUP`::/home/$JAILUSER:/bin/sh" > \
    /export/home/jail/$JAILUSER/etc/passwd
    

    and

    echo "$JAILGROUP::`/usr/xpg4/bin/id -g $JAILGROUP`:$JAILUSER" > \
    /export/home/jail/$JAILUSER/etc/group

    Top     <- Back

  25. I was very fortunate in that when this failed the first few times I tried it, I got errors telling me which library wasn't found. However, my very first few tests consisted of attempting to ssh as the chrooted user, and having it fail without giving any indication of any error, at least none that I was able to find.

  26. Top     <- Back

 

Epilogue:

I'm always open to constructive criticism about how to make these instructions more clear, more current, or even more correct. All examples are believed to run correctly under the Korn shell.

Questions, comments, clarifications, corrections, and additions may be addressed to kent@c2group.net. I attempt to properly credit all sources of information for anything referenced in this document.

 

 

 

 

 

 


Copyright © 2002 Kent Cowgill.