“A chroot on Unix operating systems is an operation that changes the apparent root directory for the current running process and its children. A program that is run in such a modified environment cannot name (and therefore normally not access) files outside the designated directory tree. The term “chroot” may refer to the chroot(2) system call or the chroot(8) wrapper program. The modified environment is called a “chroot jail”.”
Why would someone want to do this? Well sometimes a user doesn’t need access to the entire filesystem and every command to do what they need to do. In my case, I was setting up an SSH SOCKS proxy for some outside collaborators and wanted to limit access to what that SSH user could do on the command line since they didn’t need it. I’ll show you step by step how to set up a chroot jail environment after the jump.
First things first, figure out if you need to setup an SSH chroot for a single user or for a whole group. If you’re only going to do it for a single user you can skip step 1. In the guide below, I choose to do a group with a single user as this makes it easier to add SSH chroot users in the future (you’ll find out why in step 4).
Step 1 – Creating a group:
Create the group. This can be done graphically through the System Preferences -> Accounts panel, or via the command line like below. If you are doing this through the command line ensure that you don’t assign a GID which isn’t already taken:
sudo dscl . -create /Groups/socks
sudo dscl . -append /Groups/socks gid 515
Step 2 – Creating a user:
Create the local user. Again like above, you can do this graphically in the same panel as the group or through the command line. This time ensure you don’t assign a UID which isn’t already taken. Also, assign the PrimaryGroupID value of the user to the group GID you specified in step 1:
sudo dscl . -create /Users/socksproxy
sudo dscl . -appened /Users/socksproxy NFSHomeDirectory /Users/socksproxy
sudo dscl . -appened /Users/socksproxy PrimaryGroupID 515
sudo dscl . -appened /Users/socksproxy UserShell /bin/bash
sudo dscl . -passwd /Users/socksproxy "password_goes_here"
Regardless of how you created the user go ahead and run a command similar to below. You don’t necessarily need to create the jail directory but I like to have it there as reminder. If you choose not too, remember to alter whatever commands use it in the later steps:
sudo mkdir -p /Users/socksproxy/jail
Step 3 – Setting up the chroot jail environment:
Now here’s the laborious part. You need to stick all the tools (shells, libraries, commands, etc…) you want the SSH chroot user to have access to in their jail directory. And depending on how many that is, this could take awhile. Luckily to help you there’s the “otool” command. Otool is an object file displaying tool and with the “-L” switch, will display the names and version numbers of the shared libraries that the object file uses. Here’s what it looks like in action:
otool -L /bin/bash
/usr/lib/libncurses.5.4.dylib (compatibility version 5.4.0, current version 5.4.0)
/usr/lib/libiconv.2.dylib (compatibility version 7.0.0, current version 7.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 125.0.1)
From the output, we can see bash uses the libncurses.5.4.dylib, libiconv.2.dylib, and libSystem.B.dylib libraries. So what we need to do is first create these directories inside our jail folder:
sudo mkdir -p /Users/socksproxy/jail/bin /Users/socksproxy/jail/usr/lib
And then we need to copy bash and all of its dependencies:
ditto /bin/bash /Users/socksproxy/jail/bin/
ditto /usr/lib/libncurses.5.4.dylib /Users/socksproxy/jail/usr/lib/
ditto /usr/lib/libiconv.2.dylib /Users/socksproxy/jail/usr/lib/
ditto /usr/lib/libSystem.B.dylib /Users/socksproxy/jail/usr/lib/
Now you’re thinking we’re done with bash right, I can go home now? Wrong! Just like bash has dependencies, sometimes libraries have dependencies too:
otool -L /usr/lib/libSystem.B.dylib
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 125.2.11)
/usr/lib/system/libmathCommon.A.dylib (compatibility version 1.0.0, current version 315.0.0)
You know the drill:
sudo mkdir /Users/socksproxy/jail/usr/lib/system
ditto /usr/lib/system/libmathCommon.A.dylib /Users/socksproxy/jail/usr/lib/system/
One dependency that isn’t so obvious is the /usr/lib/dyld file, so make sure you copy that over to the jail folder:
ditto /usr/lib/dyld /Users/socksproxy/jail/usr/lib/
And that’s it! For bash… Try to run chroot manually as root in a bash shell inside the jail directory:
If there are no errors and the output of “pwd” returns “/” you’re golden (chroot… changing root, ha! I get it). If there were any errors you’ll need to check the system logs and fix it.
Being able to successfully chroot is important as you’ll need to test certain commands in a chroot jail environment to see if they work. Unfortunately, otool alone won’t give you all the information you need. For example if you want to include traceroute, otool only shows a dependency on /usr/lib/libSystem.B.dylib, which you should already have because of bash, great! However, when you go and try to use traceroute in the chroot jail environment you’ll get this error:
(null): open “/dev/null”: No such file or directory
Which means you’ll need to copy over /dev/null too. This is a trial and error process, and will take awhile.
Step 4 – Modifying SSH:
Here is where you get to specify which group (or user) gets a chroot jail environment for their SSH connection. Append the following to the end of /etc/sshd_config, changing the value of the Match Group option to whatever group you created in step 1 or change it to say “Match User username” if you’re going that route.
Subsystem sftp internal-sftp
Match Group socks
If a line such as “Subsystem sftp /usr/libexec/sftp-server” already exists, comment it out in favour of the one above. The %h value for the ChrootDirectory option stands for the users home. So the path where a user would end up after successfully SSH’ing would be /Users/username/jail. If you are setting this up for a SOCKS proxy user, like I was, keep the yes value for the AllowTcpForwarding option, otherwise change it to no.
Once you modify anything in a configuration file, it’s usually a good idea to restart the service tied to said file. This can be done graphically via the System Preferences -> Sharing panel by toggling Remote Login or manually like so:
sudo launchctl unload /System/Library/LaunchDaemons/ssh.plist
sudo launchctl load /System/Library/LaunchDaemons/ssh.plist
If all users aren’t allowed to SSH, you’ll need to make sure that the user you created in step 2 is allowed to. You can add them graphically to the Remote Login list in the System Preferences -> Sharing panel or manually just by adding them to the com.apple.access_ssh group:
sudo dscl . -append /Groups/com.apple.access_ssh GroupMembership socksproxy
Step 5 – Folder permissions:
In order to use chroot with SSH, all directories in the chroot path have to be owned by root and have their mode set to 755. So for /Users/socksproxy/jail that would be:
The group for each of these folders can all be different but the owner has to be root. If you don’t do this you’ll see these errors in /var/log/secure.log when your chroot user tries to SSH in:
fatal: bad ownership or modes for chroot directory component “/Users/socksproxy/”
Ensure that the permissions are set correctly otherwise your chroot user won’t be able to log in via SSH.
Step 6 – Test:
Now’s the time to test to see if your new chroot user can successfully get a shell when they SSH in. If that works you’re all done. If that doesn’t work check /var/log/secure.log for errors. If you have any errors please feel free to post them in the comments and I’ll try my best to help you figure it out.