LD_LIBRARY_PATH ignored on Android sometimes

10,333

Solution 1

Here is a simple wrapper I wrote about:

#include <android/log.h>
#include <dlfcn.h>
#include <stdio.h>
#include <string.h>

typedef int (*main_t)(int argc, char** argv);

static int help(const char* argv0)
{
    printf("%s: simple wrapper to work around LD_LIBRARY_PATH\n\n", argv0);
    printf("Args: executable, list all the libraries you need to load in dependency order, executable again, optional parameters\n");
    printf("example: %s /data/local/ttte /data/data/app/com.testwrapper/lib/ttt.so /data/local/ttte 12345\n", argv0);
    printf("Note: the executable should be built with CFLAGS=\"-fPIC -pie\", LDFLAGS=\"-rdynamic\"\n");

    return -1;
}

int main(int argc, char** argv)
{
    int rc, nlibs;
    void *dl_handle;

    if (argc < 2)
    {
        return help(argv[0]);
    }

    __android_log_print(ANDROID_LOG_DEBUG, "wrapper", "running '%s'", argv[1]);

    for (nlibs = 2; ; nlibs++)
    {
        if (nlibs >= argc)
        {
            return help(argv[0]);
        }

        __android_log_print(ANDROID_LOG_DEBUG, "wrapper", "loading '%s'", argv[nlibs]);
        dl_handle = dlopen(argv[nlibs], 0); // do not keep the handle, except for the last
        __android_log_print(ANDROID_LOG_DEBUG, "wrapper", "loaded '%s' -> %p", argv[nlibs], dl_handle);
        if (strcmp(argv[1], argv[nlibs]) == 0)
        {
            break;
        }
    }

    main_t pmain = (main_t)dlsym(dl_handle, "main");
    __android_log_print(ANDROID_LOG_DEBUG, "wrapper", "found '%s' -> %p", "main", pmain);
    rc = pmain(argc - nlibs, argv + nlibs);

//   we are exiting the process anyway, don't need to clean the handles actually

//   __android_log_print(3, "wrapper", "closing '%s'", argv[1]);
//   dlclose(dl_handle);

    return 0;
}

To keep it readable, I drop most of error handling, unessential cleanup, and handling of special cases.

Android.mk for this executable:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := wrapper
LOCAL_SRC_FILES := wrapper/main.c
LOCAL_LDLIBS    := -llog

include $(BUILD_EXECUTABLE)

Note that you must take care of deployment: packaging this wrapper into the APK, extraction to some local path (never to USB storage or to /sdcard!), marking it as executable (chmod 777).

These are the additional parameters you must supply when you build the executables you run through the wrapper. If you use ndk-build to build them, it looks as follows:

LOCAL_C_FLAGS   += -fPIC -pie
LOCAL_LDFLAGS   += -rdynamic 

Note that you don't need to chmod for these executables anymore. Another trick: you can build the secondary executables into shared libraries, and the same wrapper will continue to work! This saves the trouble of deployment of these binaries. NDK and Android build will deliver them safely through libs/armeabi of the APK to your app's lib directory automagically.

Update

There seems to be a much easier solution, using the ProcessBuilder with modified environment: https://stackoverflow.com/a/8962189/192373.

Solution 2

This is not a kernel related issue, but an LD related issue: The loading of libraries is entirely in user mode, not kernel mode, so the kernel would have nothing to do with it. The kernel is only responsible for transferring control to ld, which loops over the DYNAMIC section of the ELF binary, loading any libraries, and figuring out what to do with environment variables like LD_PRELOAD and LD_PRELOAD.

The variables, however, can pose an inherent security risk: LD_LIBRARY_PATH enables you to put your own paths before the system defaults (/lib or - in Android, /system/lib). LD_PRELOAD is even worse, because it force loads (shoves) your library into the process, whether or not the process requested it - which can lead to injection of malicious code into a sensitive process. For this reason, this is not allowed on root Setuid processes. If you are in a root shell, however (i.e. on a rooted phone), most devices will allow it. The vendor might modify the LD, leading to your problems.

What you can do to work around:

  • Easiest: remount /system as read/write (mount -o remount /system), and copy your libraries to /system/lib. Will require rooted phone

  • Smartest: Statically link your libraries into your binary, while maintaing the other (system) libraries dynamic

Hope this helps,

TG

Solution 3

That's correct, you cannot rely on LD_LIBRARY_PATH on Android. You can use System.load() with any library, but this does not really help when you spawn child processes. You can use dlopen in your code, but usually it means that many lines of code must be rewritten.

A funny trick is to convert your executables into shared libraries, and load them from wrapper executables which would dlopen the libraries in dependency order.

It is highly recommended though to convert as many spawns as possible into in-process calls.

Solution 4

I don't think the LD_LIBRARY_PATH will be ignored. The way you update the system environment is ok. The LD_LIBRARY_PATH will get updated under the root user.

The solution will not be able to work on some machine may have several causes:

  1. The Android app is always run under its own user id. It is not run under root privilege. If you only modify the system variable of the root user. The application may still cannot find the correct library path in its own envrionment.

  2. Even if you correctly updated the LD_LIBRARY_PATH when the application initialize itself, you still need to make sure the application's corresponding user id do have the access to read those libraries.

  3. Some vendor will modify the Android kernel for some security reasons. They may disable the loading of 3rd-party libraries from external storage or something like that.

Share:
10,333
Simone Margaritelli
Author by

Simone Margaritelli

Security researcher, hardcore C/C++ developer , wannabe reverser and coffee addicted.

Updated on June 04, 2022

Comments

  • Simone Margaritelli
    Simone Margaritelli almost 2 years

    i have an android app which spawns many native executables dinamically linked with libraries i distribute with the package. To launch those binaries, i use the LD_LIBRARY_PATH environment variable to make them aware of the place to load the libraries from, but on some devices this doesn't work at all, the LD_LIBRARY_PATH is correctly updated but the binary fails to find the library anyway. This is not something i can reproduce because on my two devices ( Galaxy Nexus & Nexus 7 with stock roms ) it just works fine.

    I tried many ways, for instance i spawn:

    LD_LIBRARY_PATH=/my/package/custom/libs:$LD_LIBRARY_PATH && cd /binary/directory && ./binary
    

    And :

        String[] envp = { "LD_LIBRARY_PATH=" + libPath + ":$LD_LIBRARY_PATH" };
    
        Process process = Runtime.getRuntime().exec( "su", envp );
    
        writer = new DataOutputStream( process.getOutputStream() );
        reader = new BufferedReader( new InputStreamReader( process.getInputStream() ) );
    
        writer.writeBytes( "export LD_LIBRARY_PATH=" + libPath + ":$LD_LIBRARY_PATH\n" );
        writer.flush();
    

    But on those devices nothing seemed to work ... so i'm starting to think that this is a kernel related issue, some kernels ( like mine ) use the LD_LIBRARY_PATH, other kernels don't ( simply ignore it, or they're using just the LD_LIBRARY_PATH that was set on application startup, therefore there's no way to change it at runtime ).

    I also tried to use System.load but it didn't work, probably because those libs are not JNI ... is there something i could try before starting to think about using statically linked binaries ?

  • Simone Margaritelli
    Simone Margaritelli over 11 years
    and why then this is working on some devices ( 80% ) and not working on others ? for instance, on a Huawei U8510 Ideos X3 with Android version 2.3.3 is not working.
  • Alex Cohn
    Alex Cohn over 11 years
    I didn't say "LD_LIBRARY_PATH does not work on Android". I said "you cannot rely on". I estimate the 80% as rather high. But maybe you are looking mostly at the high-end contemporary devices...
  • Simone Margaritelli
    Simone Margaritelli over 11 years
    Ok, since i can't convert my executables because the source code is really big ( we're talkin about ettercap, nmap, and so on ), and i can't convert spawns into process calls neither to avoid app freezing during process execution ... do you have any idea on how to solve this ? :)
  • Alex Cohn
    Alex Cohn over 11 years
    OK, after making some testing: if you can rebuild your executables (the ones that rely on the shared libraries you install) with -fPIC -pie flags (as described elsewhere on stackoverflow), then you can prepare your own wrapper executable (kind of replacement or rather augmentation to the system dynamic linker), which will start, dynamically load the necessary shared libraries, finally load the required executable with dlopen, and call its main() through dlsym()