Full_Name: Barry Lance Version: 2.4.35 OS: Linux (Debian 7) URL: ftp://ftp.openldap.org/incoming/ Submission from: (NULL) (70.226.37.254)
Operating System: Debian 7.1.0 (Wheezy) 64-bit Openldap version: 2.4.35
Configure Options: --prefix=/usr/local --enable-shared --enable-static --enable-debug --enable-dynamic --enable-syslog --enable-local --enable-slapd --enable-spasswd --enable-modules --enable-backends=mod --disable-ndb --disable-passwd --disable-perl --disable-shell --disable-sock --disable-sql --enable-overlays=mod --with-threads --with-cyrus-sasl --with-tls=openssl
config: slapd.conf from make install
Jail directory: /var/chroot/openldap
Files copied into jail:
/etc/openldap -> <jaildir>/etc/openldap /etc/nsswitch.cond -> <jaildir>/etc /etc/pam.d -> <jaildir>/etc/pam.d /etc/passwd (or fragment of) -> <jaildir>/etc/passwd /etc/groups (or fragment of) -> <jaildir>/etc/groups /etc/shadow -> <jaildir>/etc/shadow all other libs referenced by ldd slapd into respective <jaildir>/dir /usr/local/libexec/openldap -> <jaildir>//usr/local/libexec/openldap /lib/x86_64-linux-gnu/libnss_* -> <jaildir>/lib/x86_64-linux-gnu commandline: /usr/local/libexec/slapd -d -1 -f /etc/openldap/slapd.conf -h "ldap:/// ldapi:///" -n slapd -r /var/chroot/openldap -u ldap -g ldap
The behavior I have experienced is as follows:
1, Launch slapd without user (-u), group (-g), and jail dir (-r) options is successful. Slapd is running under the current user id (root).
2. Launch slapd with user and group parameters, but without a jail directory successful and the root privilege is dropped to the username given
Takeaway - slapd is able to read passwd and groups outside jail. This is definitely expected.
3, Launch slapd with user, group and jail dir options, slapd fails with a message that no such user exists in passwd.
4. Launching slapd given a jail directory, but no user or group options succeeds with the daemon jailed in jail dir, butrunning as root (undesirable).
Takeaway - chroot code works, but passwd/groups cannot be accessed after it (as seen in (3)). (4) is expected given the code in servers/slapd/main.c attempts to get the real uid/gid and drop root permission only if the -u and -g options are given on the command line.
Comparing servers/slapd/main.c to the code for a few other daemons (ntpd, named, isc-dhcp), the jailing process follows the chdir/chroot process as expected. The difference in these other daemon is that, in all cases, the real uid/gid are retrieved before the chroot code. By doing this, nsswitch.conf, passwd, groups, etc are all still available outside the jail. Once the chroot is completed, they then drop root permission to the non privileged user given on the command line.
The code in servers/slapd/main.c gets the real uid/gid AFTER the chroot. As such, the authentication infrastructure (nsswitch.conf, passwd, groups, etc) must be duplicated in the jail. In my opinion, this makes jailing slapd more difficult (inconvenient) than the other daemons mentioned. The jailing code in slapd may work, but I was unable to make it go as a non-root user. Didn't find very much useful information available via Google with respect to troubleshooting my chroot issue.
To test a few theories, I added some scaffolding code in main.c and user.c to see where the process was going bad for me. As expected after looking at the source, the failure was happening in the slap_init_user function of user.c. More specifically, the call to getpwnam was returning NULL (failure) and causing the corresponding Debug statement to print the error message I am seeing when attempting to jail as a non-privileged user. No surprise there.
Not being that familiar with the code in these two files, I am reluctant to modify too much for fear of introducing unintentional side effects. But in testing I found a few ways of working around the issue.
Initially, I thought it might be easiest to move the call to slap_init_user before the chroot code. But then I realized that cannot work, because this function drops root permission before returning which will then cause the chroot code to fail.
The first, and simplest, workaround I found was that by making an initial call to getpwnam and discarding the result before hitting the chroot code seemed to make the subsequent call in slap_init_user succeed. Wierd. I can only speculate two possibilities for that. The most reasonable is that the getpwnam call before the chroot code loads some shared library(ies) into memory that I'm missing in my jail allowing the later call after the chroot call to succeed. The second, and least reasonable, is that the man page for getpwnam states the returned pointer is to a static passwd struct which when initialized before the chroot code, is returned despite the later failure in slap_init_user. Like I said, least likely. I don't have the knowledge to test either theory and it just works despite the wasteful additional call to getpwnam. This workaround seems to have the least amount of potential side-effects elsewhere in the code.
The second workaround involved moving the code blocks for the getpwnam and getgrnam calls out of slap_init_user and into main just prior to the the chroot code. I would also then have to drop the root permission just after the chroot code block based on got_uid/gid variable values. I didn't dig far enough into the code to know if there is anywhere else that the code drops root permission. If so, this may cause one of those unintentional side effects. This also worked, but seemed like bad coding practice.
My third, and final, work around I think fits in the best with the spirit of the existing code. In this workaround I split up the slap_init_user code in user.c into four seperate functions: slap_init_user, slap_init_group, slap_set_user, slap_set_group. The slap_init_xxx functions use two variables added to main of type uid_t and gid_t that are passed in by reference. Each function performs the respective passwd/group calls in slap_init_user, store the result, if successful, into the byref parameter(s) and return a got_uid/gid value back as an integer return value indicating if the uid/gid parameters contain useful id's. The slapd_set_xxx functions perform the actual drop of root permission. Back in main, the slap_init_user/group functions get called prior to the chroot code and the corresponding slap_set_xxx calls are added in the relevant code block that follows the chroot call if the "got" variables indicate the uid/gid values are useful. This allows the uid/gid to be looked up prior to chroot eliminating the need for security info in the jail, root permission is still held to chroot, and the dropping of root authority sill occurs immediately after the chroot call.
At the end of the day, I think the third workaround allow the chroot code for slapd to perform in the same way as the other daemons I mentioned creating a more predictable user experience. I certainly can't speak for everyone, but to me, a predictable user experience is more definitely more convenient. In the same light, I wonder if moving the creation of the pid and args files before the chroot adds value for the user when dealing with the daemon via an init script. The init script might be a bit more simple without having to worry about prepending a jail path to the pid file detected from the config when the daemon is being run as such. Not a big issue.
If you think the code I used in my workarounds add value to the project, I will gladly send them along. But, honestly, it is so rudimentary that I doubt my copy offers you much value since I'm sure you could duplicate it in less than 30 minutes.
Thanks,
Barry Lance