As I said, the main issue is that to "intercept" a system call, you need to intercept a system call instruction like "int $0x80" in i386, "syscall" in x86_64, or "svc 0" on ARM. This is much more difficult to do, especially in a user space program, because it would ultimately require changing GDT/IDT/IVT entries in the CPU, which is not really feasible or allowed in a userspace program.
On the other hand, overriding a library function is simply just a matter of changing where the function pointers in the PLT entries point to, namely in the LD_PRELOAD library rather than to libc, which is an operation easily done in user space.
I have been considering the use of the seccomp functions SECCOMP_RET_TRAP and SECCOMP_RET_USER_NOTIF along with a "program loader" that reads the ELF header and installs a SIGSYS handler to do effectively what the LD_PRELOAD library would have done, though I'm not sure if that is a good long-term solution, especially if it requires architecture-specific assembler instructions.
That being said, my use of LD_PRELOAD is never to "decrease" the privileges of a running program. There should be other means of ensuring that the privileges are contained, such as with mount, network, and user namespaces. LD_PRELOAD is mainly intended to facilitate the use of "alternate access" where the containerized environment has access to such facilities, it just needs to be made to use it, for example, connecting to a Unix domain socket, but the app itself only supports inet sockets.
The glibc version incompatibility issue could theoretically be fixed by the use of weak symbols and by probing for the existence of those symbols at run time or by additional LD_PRELOAD libraries to retrofit in the newer-version symbols into older versions of glibc.
Hope that helps,
Peter