If the files you need to access are within a few directories you could open those directories before you chroot and save the file descriptors. You can then use the so-called *at functions (e.g. openat(), renameat(), etc.) to get at the individual files. Basically you are opening the files relative to the already open directory file descriptors rather than the chrooted directory.
Whether this is a safe thing to do is open to question but it should work in Linux.
EDIT: This is on the ugly side but it seems to work. You should poke around a lot more for vulnerabilities than I have. I haven't tested how dropping privileges and so forth will effect things.
#include <iostream>
#include <string>
using namespace std;
#include <cstdio>
#include <cstdlib>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
int main(int argc, char *argv[])
{
if (argc < 4)
{
cerr << "USAGE: " << argv[0] << " <jail directory> <freeworld directory> <filename>\n";
exit(EXIT_FAILURE);
}
const string JAILDIR(argv[1]);
const string FREEDIR(argv[2]);
string freefilename(argv[3]);
while (freefilename[0] == '/')
freefilename.erase(0, 1);
DIR *pDir;
if ((pDir = opendir(FREEDIR.c_str())) == NULL)
{
perror("Could not open outside dir");
exit(EXIT_FAILURE);
}
int freeFD = dirfd(pDir);
//cd to jail dir
if (chdir(JAILDIR.c_str()) == -1)
{
perror("cd before chroot");
exit(EXIT_FAILURE);
}
//lock in jail
if (chroot(JAILDIR.c_str()) < 0)
{
cerr << "Failed to chroot to " << JAILDIR << " - " << strerror(errno) << endl;
exit(EXIT_FAILURE);
}
//
//in jail, won't work
//
string JailFile(FREEDIR);
JailFile += "/";
JailFile += freefilename;
int jailFD;
if ((jailFD = open(JailFile.c_str(), O_RDONLY)) == -1)
{
cout << "as expected, could not open " << JailFile << endl;
perror("exected open fail");
}
else
{
cout << "defying all logic, opened " << JailFile << endl;
exit(EXIT_FAILURE);
}
//
//using this works
//
if ((jailFD = openat(freeFD, freefilename.c_str(), O_RDONLY)) == -1)
{
cout << "example did not work. Could not open " << freefilename << " Sorry!" << endl;
exit(EXIT_FAILURE);
}
else
cout << "opened " << freefilename << " from inside jail" << endl;
char buff[255];
ssize_t numread;
while (1)
{
if ((numread = read(jailFD, buff, sizeof(buff) - 1)) == -1)
{
perror("read");
exit(EXIT_FAILURE);
}
if (numread == 0)
break;
buff[numread] = '\0';
cout << buff << endl;
}
return 0;
}
To test:
echo "Hello World" >/tmp/mystuff.dat
mkdir /tmp/jail
sudo ./myprog /tmp/jail /tmp mystuff.dat