A chroot jail is not easy to break out of. That's just FUD spread by people who disagreed with the entire notion of sandboxing years ago, back when OpenBSD began its privilege separation campaign.

By definition, a chroot jail requires dropping root privileges. In almost all cases, the process enters the jail early in the startup process, long before it might create a descriptor reference to a directory outside the jail. Here's a typical pattern:

int main(int argc, char **argv) {
const char *rootdir = "/var/empty";
const char *user = "_foo";
int optc;
while (-1 != getopt(argc, argv, "r:u:")) {
switch (optc) {
case 'r':
rootdir = optarg;
break;
case 'u':
user = optarg;
break;
}
}
struct passwd *pw;
if (!(pw = getpwnam(user)))
err(1, "%s", user);
/* open any config files here */
if (chroot(rootdir) != 0 || chdir("/") != 0)
err(1, "%s", rootdir);
/* optionally call setgid and setgroups here */
if (0 != setuid(pw->pw_uid)
err(1, "%s", user);
...
}

If you have don't root privileges, you cannot call chroot. If you don't have any open directory descriptors outside the jail before the chroot, you cannot acquire them after the chroot without cooperation of a process outside the chroot.

A chroot jail doesn't guarantee security. A chroot-jailed process could exploit a flaw in a daemon by connecting to its port, for example. But this applies to every other kind of sandboxing unless you define that method as "doing everything necessary to avoid criticism".

A chroot-jailed process could exploit signal bugs in other processes. But a chroot jail necessarily involves setting the real, saved, and effective UID to some non-root user, in which case you can only send signals to other processes with the same UID. When using a chroot jail it's common to switch to a unique user, like _service_name; and all such user processes would be in their own jail.

A chroot-jailed process could exploit processes sharing the same controlling TTY. But chroot-jailed processes are usually started by init and won't have a controlling TTY to begin with. If an administrator with root privileges does unsupported things, there's little you can do. In any event, it's common for daemons to close or redirect stdin, stdout, and stderr, and to call setsid, which solves the issue in most cases.

The benefit of a chroot jail is precisely it's simplicity. You're not depending on esoteric, native extensions, with complex semantics. And a chroot jail doesn't exclude additional mitigations, like lowering resource limits, enabling syscall filters, etc.

And unlike more complex namespace containers, all the bugs of chroot have been shaken out of the kernels over the decades. If you care about security, I would compile _out_ all the container crap from a modern Linux kernel. Arguably all that code bloat has caused more security problems than it's solved. And the shift in Linux to allowed non-root processes to change namespaces has and will continue to introduce all manner of security bugs. There's a reason both chroot and, until recently, similar namespace modification interfaces have required root privileges--Unix just wasn't designed to support ad hoc namespaces like in Plan 9, and it's easy to introduce subtle security issues.