2

Our Android software uses a virtual file system (VFS) for SQLite, which has been working correctly. Once we began using it with Android 6 (Marshmallow) all sorts of weird errors started to occur with large negative offsets being passed to ftruncate(), stack overflows, data corruptions, etc. Using readelf (among other tools), we eventually traced the problem to a change in the imports used by libsqlite.so: Lollipop and earlier import ftruncate and mmap, the newest libraries import ftruncate64 and mmap64. We "solved" the problem by changing which functions we use depending on the API version (Marshmallow is version 23):

/*
 * Empirical testing of Tab S2 running Marshmallow revealed the SQLite
 * unix_syscall table uses "ftruncate" and "mmap" as connection points,
 * but the actual functions linked against are the *64 versions.  This
 * leads to stack corruption and all sorts of nasty errors as a result.
 */
if (getApiVersion() >= 23)  // for Marshmallow
{   setUnixSystemCall(NULL, "ftruncate", our_ftruncate64);
    setUnixSystemCall(NULL, "mmap", our_mmap64);
}
else                        // for Lollipop & older
{   setUnixSystemCall(NULL, "ftruncate", our_ftruncate);
    setUnixSystemCall(NULL, "mmap", our_mmap);
}

Looking at the source code both from http://www.sqlite.org/2015/sqlite-amalgamation-3081002.zip and https://github.com/android/platform_external_sqlite/blob/master/dist/sqlite3.c all that the C source calls is ftruncate and mmap which makes our methodology "questionable" at best.

How does libsqlite.so import and use ftruncate64 and mmap64 where the source code only calls ftruncate and mmap? Are we not looking at the correct source code repository? Is there something going on at a link step? Did Marshmallow remove support for the non-64 bit versions of these functions?

Fred Koschara
  • 335
  • 3
  • 16

1 Answers1

1

It turns out the headers in the NDK don't exactly match the corresponding headers that the OS is ​built with!

Bionic: https://android.googlesource.com/platform/bionic.git/+/marshmallow-release/libc/include

Here's the way to BUILD the NDK: https://android.googlesource.com/platform/ndk/+/marshmallow-release

In particular,

https://android.googlesource.com/platform/bionic.git/+/marshmallow-release/libc/include/unistd.h

#if defined(__USE_FILE_OFFSET64)
extern int truncate(const char *, off_t) __RENAME(truncate64);
extern off_t lseek(int, off_t, int) __RENAME(lseek64);
extern ssize_t pread(int, void *, size_t, off_t) __RENAME(pread64);
extern ssize_t pwrite(int, const void *, size_t, off_t) __RENAME(pwrite64);
extern int ftruncate(int, off_t) __RENAME(ftruncate64);

https://android.googlesource.com/platform/bionic.git/+/marshmallow-release/libc/include/sys/mman.h has similar macros for mmap - the __RENAME() in the system headers means that any code built using the system headers (e.g., libc.so) will only export ftruncate64, not ftruncate, and when an app that calls ftruncate is linked against libc.so, it instead imports ftruncate64 rather than the call the source code was written with.

We didn't dive into the __RENAME() macro to investigate how this magic happens - the reality of trying to get a product out the door prohibits how deep we can go down the rabbit hole. If anybody wants to investigate this further, however, this is where you start.

Fred Koschara
  • 335
  • 3
  • 16