Use LD_PRELOAD to alter library calls
I have a program that is failing because it is making a system call to change permissins on a file with chmod, fchmodat, and fchmod -S 2. The program does not own the file, and doesn’t need to change the permission but fails with -1 EPERM on this call. This was determined with strace -f -e chmod,fchmod,fchmodat $program
Example program
This program simulates the behaviour of the application with the issue.
#include <stdio.h>
#include <sys/stat.h>
int main(int argc, char* argv[]) {
FILE *example = fopen("example.txt", "r");
FILE *curwd = fopen("./","r");
int result1 = fchmod(fileno(example), S_IROTH | S_IRUSR);
int result2 = chmod("example.txt", S_IROTH | S_IRUSR);
int result3 = fchmodat(fileno(curwd), "example.txt", S_IROTH | S_IRUSR, 0);
printf("%d, %d, %d\n", result1, result2, result3);
fclose(example);
fclose(curwd);
}We can simulate the error from the application by running this example program with a file that it is unable to work on.
aun@silent:~/src/preload-chmod$ cc example.c -o example
aun@silent:~/src/preload-chmod$ touch example.txt
aun@silent:~/src/preload-chmod$ sudo chown root:root example.txt
aun@silent:~/src/preload-chmod$ strace -f -e chmod,fchmod,fchmodat ./example
fchmod(3, 0404) = -1 EPERM (Operation not permitted)
chmod("example.txt", 0404) = -1 EPERM (Operation not permitted)
fchmodat(4, "example.txt", 0404) = -1 EPERM (Operation not permitted)
-1, -1, -1
+++ exited with 0 +++Research the example
I’m planning to use LD_PRELOAD from ld.so1 -S 8. The man page states “A list of additional, user-specified, ELF shared objects to be loaded before all others. This feature can be used to selectively over‐ride functions in other shared objects.” So, we need to check that this is indeed loaded from a shared library. We can use some of the ld related tools to confirm this. Results of example show below. As you can see the functions are from the GLIBC dynamic library and can be over-ridden.
aun@silent:~/src/preload-chmod$ objdump -T example
example: file format elf64-x86-64
DYNAMIC SYMBOL TABLE:
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.34 __libc_start_main
0000000000000000 w D *UND* 0000000000000000 Base _ITM_deregisterTMCloneTable
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 fclose
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 printf
0000000000000000 w D *UND* 0000000000000000 Base __gmon_start__
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 fileno
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.4 fchmodat
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 fchmod
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 chmod
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 fopen
0000000000000000 w D *UND* 0000000000000000 Base _ITM_registerTMCloneTable
0000000000000000 w DF *UND* 0000000000000000 GLIBC_2.2.5 __cxa_finalizeCreate a dl so for use by LD_PRELOAD
In this case we want to wrap the calls to the various chmod functions and return success and take no action. in the example for chmod the dlsym function is called to find the real implementation of chmod if that is needed. Calling the real_chmod function loaded from dlsym will proxy the call the actual implementation.
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/stat.h>
#include <dlfcn.h>
//gcc -shared -fPIC -ldl chmod_preload.c -o chmod_preload.so
typedef int (*fchmod_t)(int fd, mode_t mode);
fchmod_t real_fchmod;
typedef int (*fchmodat_t)(int dirfd, const char *pathname, mode_t mode, int flags);
fchmodat_t real_fchmodat;
typedef int (*chmod_t)(const char *pathname, mode_t mode);
chmod_t real_chmod;
int chmod(const char *pathname, mode_t mode) {
printf("chmod wrapper\n");
if (!real_chmod) {
real_chmod = dlsym(RTLD_NEXT, "chmod");
}
// insert your logic here.
// in this case no action is taken and success is returned
// return real_chmod(pathname, mode);
return 0;
}
int fchmodat(int dirfd, const char *pathname, mode_t mode, int flags) {
// do nothing return success
printf("fchmodat wrapper\n");
return 0;
}
int fchmod(int fd, mode_t mode) {
// do nothing return success
printf("fchmod wrapper\n");
return 0;
}Now to use the LD_PRELOAD so. You can see an example of the example program failing with -1 EPERM errors with no changes. Setting LD_PRELOAD so that ld.so can inject the library to intercept the call we see that the messages are printed as well as the success results. A shared library similar to this example was used to workaround the original issue described until an application fix was avilable.
aun@silent:~/src/preload-chmod$ ./example
-1, -1, -1
aun@silent:~/src/preload-chmod$ LD_PRELOAD=$PWD/chmod_preload.so ./example
fchmod wrapper
chmod wrapper
fchmodat wrapper
0, 0, 0Enjoy!