diff --git a/CHANGELOG.md b/CHANGELOG.md index 55ce47ee0..4442e2979 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ Features: * Battery level and charging state are now available to apps; detailed battery information requires root or ADB. (Battery) * Added display mode, physical size, rotation, and HDR information. App-based detection requires Android 13 (API 33); root or ADB can also detect displays on older versions. (Display) * Added Wi-Fi interface, connection, standard, and security information. Android 11 (API 30) or later is required. Termux:API and location permission are needed to reveal SSID and BSSID; otherwise, they appear as ``. (Wifi) + * Added Wallpaper detection on Android, which effectively reports `/data/system/users/0/wallpaper`. (Wallpaper) * Improved macOS support: * Improved process-name detection in Top. (Top) * Improved wallpaper detection on macOS Sonoma and later. (#2559, Wallpaper) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a7d4035a..d44f3f77b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -638,6 +638,7 @@ elseif(ANDROID) list(APPEND LIBFASTFETCH_SRC src/common/android/binder.c src/common/android/dex.c + src/common/android/package.c src/common/impl/dbus.c src/common/impl/io_unix.c src/common/impl/netif_linux.c @@ -712,6 +713,7 @@ elseif(ANDROID) src/detection/tpm/tpm_nosupport.c src/detection/uptime/uptime_linux.c src/detection/users/users_linux.c + src/detection/wallpaper/wallpaper_android.c src/detection/wallpaper/wallpaper_linux.c src/detection/wifi/wifi_android.c src/detection/wm/wm_linux.c diff --git a/src/common/android/binder.c b/src/common/android/binder.c index 519ba6e46..7de6972b7 100644 --- a/src/common/android/binder.c +++ b/src/common/android/binder.c @@ -47,11 +47,12 @@ void ffBinderClose(FFBinder* binder) { binder->sharedSize = 0; } -const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, const FFBinderParcel* parcel, FFBinderReply* reply) { +const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, uint32_t flags, const FFBinderParcel* parcel, FFBinderReply* reply) { reply->size = 0; reply->code = 0; reply->flags = 0; reply->handleCount = 0; + reply->fdCount = 0; if (parcel->truncated) { return "Binder parcel does not fit the caller buffer"; @@ -62,7 +63,7 @@ const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, c .target = { .handle = handle }, .cookie = 0, .code = code, - .flags = 0, // a synchronous call; TF_ONE_WAY is the only flag the kernel acts on here + .flags = flags, .sender_pid = 0, .sender_euid = 0, .data_size = parcel->size, @@ -146,8 +147,10 @@ const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, c } } - // Walk the offset table for the flat objects the service sent us and keep the - // strong handles: those are the ones a later transaction can address. + // Walk the offset table for the flat objects the service sent us. A reply may carry + // both kinds at once and they are not interchangeable: a handle is a reference that + // has to be acquired (see below) to outlive the reply buffer, while a file + // descriptor was installed in our own fd table by the kernel and is simply ours. for (size_t offset = 0; offset + sizeof(binder_size_t) <= offsetsSize; offset += sizeof(binder_size_t)) { binder_size_t objectOffset = 0; memcpy(&objectOffset, offsets + offset, sizeof(objectOffset)); @@ -156,11 +159,17 @@ const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, c } struct flat_binder_object object; memcpy(&object, payload + objectOffset, sizeof(object)); - if (object.hdr.type != BINDER_TYPE_HANDLE) { - continue; - } - if (reply->handleCount < FF_BINDER_MAX_HANDLES) { - reply->handles[reply->handleCount++] = object.handle; + if (object.hdr.type == BINDER_TYPE_HANDLE) { + if (reply->handleCount < FF_BINDER_MAX_HANDLES) { + reply->handles[reply->handleCount++] = object.handle; + } + } else if (object.hdr.type == BINDER_TYPE_FD) { + if (reply->fdCount < FF_BINDER_MAX_FDS) { + reply->fds[reply->fdCount++] = (int32_t) object.handle; + } else { + // Nothing is going to hold this one and nothing is going to close it. + close((int) object.handle); + } } } @@ -224,7 +233,7 @@ const char* ffBinderLookupService(FFBinder* binder, const char* name, uint32_t t uint8_t replyBuffer[256]; FFBinderReply reply = ffBinderReplyCreate(replyBuffer, sizeof(replyBuffer)); - const char* error = ffBinderTransact(binder, FF_BINDER_SERVICE_MANAGER_HANDLE, transactionCode, &parcel, &reply); + const char* error = ffBinderTransact(binder, FF_BINDER_SERVICE_MANAGER_HANDLE, transactionCode, 0, &parcel, &reply); if (error != nullptr) { return error; } diff --git a/src/common/android/binder.h b/src/common/android/binder.h index 91dd25032..ae8c9d47a 100644 --- a/src/common/android/binder.h +++ b/src/common/android/binder.h @@ -51,6 +51,12 @@ // the surplus handles are left unacquired and will fail on first use. #define FF_BINDER_MAX_HANDLES 4 +// How many file descriptors a reply may hand over. A descriptor is not a handle: the kernel installs +// it in the caller's own fd table as it delivers the reply, so it is owned rather than borrowed and +// the caller closes it. A reply carrying more than this still succeeds, and the surplus is closed +// again by ffBinderTransact() because nothing would be left holding it. +#define FF_BINDER_MAX_FDS 4 + // The three int32 values Parcel::writeInterfaceToken() puts in front of the descriptor, and that // Parcel::enforceInterface() checks the last of. These come from libbinder rather than from the // kernel, which is why they are spelled out here. @@ -159,10 +165,12 @@ typedef struct FFBinderReply { uint32_t flags; // TF_* of the reply uint32_t handleCount; uint32_t handles[FF_BINDER_MAX_HANDLES]; + uint32_t fdCount; + int32_t fds[FF_BINDER_MAX_FDS]; // owned by the caller, which closes them } FFBinderReply; [[nodiscard]] static inline FFBinderReply ffBinderReplyCreate(uint8_t* data, size_t capacity) { - return (FFBinderReply) { .data = data, .capacity = capacity, .size = 0, .code = 0, .flags = 0, .handleCount = 0, .handles = {} }; + return (FFBinderReply) { .data = data, .capacity = capacity, .size = 0, .code = 0, .flags = 0, .handleCount = 0, .handles = {}, .fdCount = 0, .fds = {} }; } // A reply carrying TF_STATUS_CODE is not an AIDL parcel at all: the first four bytes are the raw @@ -196,13 +204,21 @@ typedef struct FFBinderReply { return value; } -// Synchronous transaction. Returns nullptr on success, a static message otherwise. Handles the -// reply carries are acquired and added to `reply->handles` before the reply buffer is freed. +// Synchronous transaction. Returns nullptr on success, a static message otherwise. +// +// `flags` carries the TF_* values for the request, and one of them is not cosmetic: a reply that +// carries a file descriptor is only delivered at all when the request asked with TF_ACCEPT_FDS. A +// request that did not gets BR_FAILED_REPLY instead, which reads like a service that is not running +// rather than like a missing flag. TF_ONE_WAY is the other one the kernel acts on, and it is what +// makes a call fire-and-forget -- there is then no reply to fill `reply` from. +// +// Handles the reply carries are acquired and added to `reply->handles` before the reply buffer is +// freed; descriptors are added to `reply->fds`, which the caller owns and closes. // // `handle` 0 is the service manager; every other handle comes from ffBinderLookupService(). A // service that never replies blocks this call forever -- none of the services fastfetch reads does, // but there is no timeout here to fall back on. -[[gnu::nonnull(1, 4, 5), nodiscard]] const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, const FFBinderParcel* parcel, FFBinderReply* reply); +[[gnu::nonnull(1, 5, 6), nodiscard]] const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, uint32_t flags, const FFBinderParcel* parcel, FFBinderReply* reply); // --------------------------------------------------------------------------------------------- // Service manager diff --git a/src/common/android/package.c b/src/common/android/package.c new file mode 100644 index 000000000..d62efbbaa --- /dev/null +++ b/src/common/android/package.c @@ -0,0 +1,65 @@ +#include "common/android/package.h" +#include "common/android/api.h" +#include "common/debug.h" +#include "common/strutil.h" + +#include +#include + +// There is no way for a process to ask for its own package name: it is not in /proc/self/status, and +// an app cannot list /data/data. The executable path carries it instead -- an app's binaries live +// under /data/data// or /data/user///, and /proc/self/exe resolves there. +// Under Termux that is /data/data/com.termux/files/usr/bin/, whose third component is the +// package. +// +// The memory cgroup carries the same information (4:memory:/apps/com.termux on the device this was +// verified on), but the unified hierarchy spells it out as /apps/uid_10478/pid_26943 with no name +// left in it, so the executable is the portable half of the two. +bool ffAndroidGetOwnPackage(char* buffer, size_t capacity) { + char path[4096]; + const ssize_t length = readlink("/proc/self/exe", path, sizeof(path) - 1); + if (length <= 0) { + FF_DEBUG("Cannot read /proc/self/exe, so the calling package cannot be derived"); + return false; + } + path[length] = '\0'; + + const char* rest = nullptr; + const char* const dataPrefix = "/data/data/"; + const char* const userPrefix = "/data/user/"; + if (ffStrStartsWith(path, dataPrefix)) { + rest = path + strlen(dataPrefix); + } else if (ffStrStartsWith(path, userPrefix)) { + // /data/user///... -- the user number has to be stepped over before the + // package name starts, and only strchr() answers where that is. + rest = strchr(path + strlen(userPrefix), '/'); + if (rest != nullptr) { + rest += 1; + } + } + + if (rest == nullptr) { + const uint32_t uid = instance.state.platform.uid; + FF_DEBUG("The executable is not in an app data directory (\"%s\"), uid %u", path, uid); + if (uid != FF_ANDROID_PRIVILEGED_UID_SHELL) { + return false; + } + static const char shellPackage[] = FF_ANDROID_SHELL_PACKAGE; + if (sizeof(shellPackage) > capacity) { + return false; + } + memcpy(buffer, shellPackage, sizeof(shellPackage)); + FF_DEBUG("The calling package is \"%s\" (the shell owns it)", buffer); + return true; + } + + const char* end = strchr(rest, '/'); + const size_t nameLength = end != nullptr ? (size_t) (end - rest) : strlen(rest); + if (nameLength == 0 || nameLength >= capacity) { + return false; + } + memcpy(buffer, rest, nameLength); + buffer[nameLength] = '\0'; + FF_DEBUG("The calling package is \"%s\"", buffer); + return true; +} diff --git a/src/common/android/package.h b/src/common/android/package.h new file mode 100644 index 000000000..e5f3241aa --- /dev/null +++ b/src/common/android/package.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +// The one package name the shell UID owns. android.os.Process.SHELL_UID is a single app UID that +// owns exactly one package, so a process that cannot derive a package of its own -- a static build +// pushed to /data/local/tmp is the case that showed up -- can answer with this name and have the +// system services accept it. See FF_ANDROID_PRIVILEGED_UID_SHELL in common/android/api.h. +#define FF_ANDROID_SHELL_PACKAGE "com.android.shell" + +// Some of the system services take the caller's package name as an argument and check it against the +// calling UID rather than against the name being real: a name nobody owns, and a real name belonging +// to another app, are refused exactly like a null one. So it has to be the caller's own name. +// +// Whether the name is checked at all depends on the service and the release -- `getConnectionInfo` +// answered "Package com.termux does not belong to " on one Android 16 device while the +// same name was accepted on an Android 11 one, and `getWallpaper` refuses a null, empty or foreign +// name on that Android 16 device -- so passing the real one is what works everywhere. +// +// Writes the name into `buffer`, terminator included, and returns whether one was found. `capacity` +// is the caller's, because the parcel the name ends up in is sized from it. +[[gnu::nonnull(1), nodiscard]] bool ffAndroidGetOwnPackage(char* buffer, size_t capacity); diff --git a/src/detection/battery/battery_android.c b/src/detection/battery/battery_android.c index d750c07f1..369e1372a 100644 --- a/src/detection/battery/battery_android.c +++ b/src/detection/battery/battery_android.c @@ -120,7 +120,7 @@ static const char* getProperty(FFBinder* binder, uint32_t handle, uint32_t prope uint8_t replyBuffer[128]; FFBinderReply reply = ffBinderReplyCreate(replyBuffer, sizeof(replyBuffer)); - const char* error = ffBinderTransact(binder, handle, transaction, &parcel, &reply); + const char* error = ffBinderTransact(binder, handle, transaction, 0, &parcel, &reply); if (error != nullptr) { FF_DEBUG("Property %u could not be transacted: %s", property, error); return error; diff --git a/src/detection/wallpaper/wallpaper_android.c b/src/detection/wallpaper/wallpaper_android.c new file mode 100644 index 000000000..5f89d74de --- /dev/null +++ b/src/detection/wallpaper/wallpaper_android.c @@ -0,0 +1,167 @@ +#include "wallpaper.h" +#include "common/android/binder.h" +#include "common/android/package.h" +#include "common/debug.h" +#include "common/io.h" +#include "detection/displayserver/displayserver.h" + +#include + +// Android keeps the wallpaper as a file per user, in a directory no app can enter: +// +// /data/system/users//wallpaper the image the launcher draws +// /data/system/users//wallpaper_orig the picture as it was uploaded, before cropping +// +// /data/system/users/ is mode 0700 and owned by system:system, so `ls`, `stat` and `open` all +// answer EACCES to an app UID: the path is not readable even though it is not secret. What is +// reachable is a descriptor to the file, because android.app.IWallpaperManager opens it inside +// system_server and passes one back, and a descriptor is an ordinary file from then on. readlink(2) +// on it names the file, which is the string this module is asked for -- the image itself is never +// read or decoded. +// +// The gate is the calling package. `getWallpaper(String callingPackage, int which, int userId)` +// answers only when that name is the caller's own: the two-argument overload, which is the null name, +// comes back as a SecurityException raised by StorageManager.checkPermissionReadImages(), and an +// empty string, an unknown package and a real package belonging to a different uid all leave with no +// descriptor. That READ_MEDIA_IMAGES denial is not something a Termux-style app can lift -- the +// permission is not in its manifest and it targets SDK 28 -- so naming the caller is the only route. +// +// Measured on an Android 16 device as an app UID: the descriptor for `which` 1 is +// /data/system/users/0/wallpaper, a 2833949-byte JPEG whose SOF0 reads 2159x3168, while `which` 2 +// answers with no descriptor at all because that lock screen is a live wallpaper. +// +// Only one of the two display servers is ever asked. The binder route is Android's, and it answers +// while SurfaceFlinger is the display server; a Termux:X11 or Wayland session puts a real compositor +// on top, and there the wallpaper belongs to that desktop -- so that case goes to wallpaper_linux.c, +// which is compiled alongside this file for exactly this reason. + +#define FF_WALLPAPER_ANDROID_SERVICE "wallpaper" +#define FF_WALLPAPER_ANDROID_DESCRIPTOR "android.app.IWallpaperManager" + +// WallpaperManager.FLAG_SYSTEM and FLAG_LOCK. +typedef enum FFWallpaperAndroidWhich : int32_t { + FF_WALLPAPER_ANDROID_WHICH_SYSTEM = 1, + FF_WALLPAPER_ANDROID_WHICH_LOCK = 2, +} FFWallpaperAndroidWhich; + +// `getWallpaper` is the fourth method of IWallpaperManager$Stub, and the number is read from the +// device's own framework.jar rather than transcribed from AOSP, because a vendor ROM adds and drops +// methods as it likes -- this one dropped `getName`, whose code answers UNKNOWN_TRANSACTION here. +#define FF_WALLPAPER_ANDROID_TRANSACTION_GET_WALLPAPER 4u + +// The request is the interface token, the package name, `which` and `userId`. The package name is the +// longest thing in it, and the helper will not write more than 128 bytes of it. +#define FF_WALLPAPER_ANDROID_PARCEL_SIZE 256 + +// The reply carries the descriptor object plus, for this method, a Bundle of two ints: 108 bytes on +// the device it was measured on, 80 when the wallpaper is a live one and the Bundle comes alone. Only +// the descriptor table is read out of it, so this is sized for headroom rather than for a field -- +// being wrong here would fail the detection, not misread it. +#define FF_WALLPAPER_ANDROID_REPLY_SIZE 512 + +static const char* getWallpaperFile(FFBinder* binder, uint32_t handle, const char* package, FFWallpaperAndroidWhich which, FFstrbuf* result) { + uint8_t parcelBuffer[FF_WALLPAPER_ANDROID_PARCEL_SIZE]; + FFBinderParcel parcel = ffBinderParcelCreate(parcelBuffer, sizeof(parcelBuffer)); + ffBinderParcelPutInterfaceToken(&parcel, FF_WALLPAPER_ANDROID_DESCRIPTOR); + ffBinderParcelPutString16(&parcel, package); + ffBinderParcelPutI32(&parcel, (int32_t) which); + ffBinderParcelPutI32(&parcel, 0); // userId, which is the one user a phone has + + uint8_t replyBuffer[FF_WALLPAPER_ANDROID_REPLY_SIZE]; + FFBinderReply reply = ffBinderReplyCreate(replyBuffer, sizeof(replyBuffer)); + + // TF_ACCEPT_FDS is neither optional nor a hint: a reply carrying a descriptor is only delivered + // when the request asked for one, and a request that did not is answered with BR_FAILED_REPLY -- + // which reads like a service that is not running rather than like a missing flag. + const char* error = ffBinderTransact(binder, handle, FF_WALLPAPER_ANDROID_TRANSACTION_GET_WALLPAPER, TF_ACCEPT_FDS, &parcel, &reply); + + // Whatever else happened, a descriptor that did arrive is ours: the kernel installed it in our fd + // table, and on the paths below that return early nothing else would close it. + FF_AUTO_CLOSE_FD FFNativeFD fd = reply.fdCount > 0 ? reply.fds[0] : -1; + + if (error != nullptr) { + FF_DEBUG("getWallpaper(which %d) could not be transacted: %s", (int) which, error); + return error; + } + if (ffBinderReplyIsStatus(&reply)) { + FF_DEBUG("getWallpaper(which %d) came back as a status reply of %d instead of a parcel", + (int) which, (int) ffBinderReadI32(reply.data, reply.size, 0)); + return "Wallpaper service rejected the request"; + } + + const int32_t exception = ffBinderReadI32(reply.data, reply.size, 0); + if (exception != 0) { + // -1 is a SecurityException, and for this method it is what the package name check raises. + FF_DEBUG("getWallpaper(which %d) raised exception %d over %zu bytes of reply", (int) which, exception, reply.size); + return "Wallpaper service refused the request"; + } + if (!ffIsValidNativeFD(fd)) { + FF_DEBUG("getWallpaper(which %d) carried no descriptor, which is how a live wallpaper answers: " + "%zu bytes of reply", + (int) which, reply.size); + return "The wallpaper is not a file"; + } + + char path[64]; + const int pathLength = snprintf(path, sizeof(path), "/proc/self/fd/%d", (int) fd); + if (pathLength <= 0 || (size_t) pathLength >= sizeof(path)) { + return "Failed to build the /proc path of the wallpaper descriptor"; + } + + char target[512]; + const ssize_t length = readlink(path, target, sizeof(target) - 1); + if (length <= 0) { + FF_DEBUG("readlink(%s) failed", path); + return "Failed to resolve the wallpaper path"; + } + target[length] = '\0'; + + ffStrbufSetNS(result, (uint32_t) length, target); + FF_DEBUG("The wallpaper for which %d is \"%s\"", (int) which, result->chars); + return nullptr; +} + +const char* ffDetectWallpaper(FFstrbuf* result) { + // The binder route below reads Android's own wallpaper, which is the one behind the launcher + // only while SurfaceFlinger is the display server. A Termux:X11 or Wayland session runs a real + // compositor on top of the device, and the wallpaper visible there belongs to that desktop, not + // to Android -- so that case is handed to the Linux implementation, which knows GTK and Qt + // settings. Wallpaper is only reported for one of the two, never merged. + const FFDisplayServerResult* wm = ffConnectDisplayServer(); + if (!ffStrbufIgnCaseEqualS(&wm->wmProtocolName, FF_WM_PROTOCOL_SURFACEFLINGER)) { + FF_DEBUG("The display server is \"%s\", so the Linux implementation answers", wm->wmProtocolName.chars); + const char* ffDetectWallpaperLinux(FFstrbuf* result); + return ffDetectWallpaperLinux(result); + } + + char package[128]; + if (!ffAndroidGetOwnPackage(package, sizeof(package))) { + return "Cannot determine the package name of this process"; + } + + [[gnu::cleanup(ffBinderClose)]] FFBinder binder = { .fd = -1 }; + const char* error = ffBinderOpen(&binder); + if (error != nullptr) { + return error; + } + + // Released on every path out of this function, including the ones below that return early. + [[gnu::cleanup(ffBinderServiceHandleRelease)]] FFBinderServiceHandle service = { .binder = &binder }; + error = ffBinderLookupService(&binder, FF_WALLPAPER_ANDROID_SERVICE, FF_BINDER_SM_GET_SERVICE, &service.handle); + if (error != nullptr) { + FF_DEBUG("Looking up the \"%s\" service failed: %s", FF_WALLPAPER_ANDROID_SERVICE, error); + return error; + } + FF_DEBUG("The \"%s\" service is handle %u", FF_WALLPAPER_ANDROID_SERVICE, service.handle); + + // The system wallpaper is what the module means by "the current wallpaper"; the lock screen is a + // second, separate picture, and is only reported when the system one is not a file -- which is + // the live-wallpaper case, where a component draws it and there is no image behind it. + error = getWallpaperFile(&binder, service.handle, package, FF_WALLPAPER_ANDROID_WHICH_SYSTEM, result); + if (error == nullptr) { + return nullptr; + } + + FF_DEBUG("The system wallpaper is not a file (%s), so the lock screen is asked for instead", error); + return getWallpaperFile(&binder, service.handle, package, FF_WALLPAPER_ANDROID_WHICH_LOCK, result); +} diff --git a/src/detection/wallpaper/wallpaper_linux.c b/src/detection/wallpaper/wallpaper_linux.c index cde966254..b63f93ab1 100644 --- a/src/detection/wallpaper/wallpaper_linux.c +++ b/src/detection/wallpaper/wallpaper_linux.c @@ -72,7 +72,16 @@ static const char* detectCosmicComp(FFstrbuf* result) { return nullptr; } -const char* ffDetectWallpaper(FFstrbuf* result) { +// Android compiles this file next to wallpaper_android.c, which owns ffDetectWallpaper and calls +// this one only while the display server is not SurfaceFlinger -- so the entry point has to step +// aside there. Same arrangement as terminalfont_linux.c and terminalfont_android.c. +const char* +#ifdef __ANDROID__ +ffDetectWallpaperLinux +#else +ffDetectWallpaper +#endif + (FFstrbuf* result) { const FFDisplayServerResult* wm = ffConnectDisplayServer(); if (ffStrbufIgnCaseEqualS(&wm->wmPrettyName, FF_WM_PRETTY_COSMIC_COMP)) { return detectCosmicComp(result); diff --git a/src/detection/wifi/wifi_android.c b/src/detection/wifi/wifi_android.c index 960bfdfc0..3cb2a4377 100644 --- a/src/detection/wifi/wifi_android.c +++ b/src/detection/wifi/wifi_android.c @@ -3,7 +3,9 @@ #include "common/android/api.h" #include "common/android/binder.h" #include "common/android/dex.h" +#include "common/android/package.h" #include "common/debug.h" +#include "common/strutil.h" #include #include @@ -153,11 +155,6 @@ typedef enum FFWifiAndroidRadioState : int32_t FF_WIFI_ANDROID_WIFI_STATE_UNKNOWN = 4, } FFWifiAndroidRadioState; -// The other argument is the caller's package name. The shell UID owns exactly one package and the -// service accepts that name, which is what `cmd` and `dumpsys` pass. The UID itself is -// FF_ANDROID_PRIVILEGED_UID_SHELL, see common/android/api.h. See getOwnPackage(). -#define FF_WIFI_ANDROID_SHELL_PACKAGE "com.android.shell" - // An AIDL reply opens with the exception code and, for a Parcelable return, a non-null marker; // WifiInfo itself then starts at 8. The network id, the RSSI and the link speed have held those // offsets from Android 9 to 16, and the transmit speed joined them in Android 10 without moving @@ -290,64 +287,9 @@ typedef enum FFWifiAndroidStandard : int32_t // Binder // --------------------------------------------------------------------------------------------- -// `getConnectionInfo` takes the caller's package name, and whether it is checked depends on the -// release: the Android 16 device answered "Package com.termux does not belong to " for a name -// the shell does not own, while the Android 11 device accepted the same name. Passing the real one is -// what works on both. There is no way for a process to ask for its own package name -- it is not in -// /proc/self/status, and an app cannot list /data/data -- but the executable path carries it: an app's -// binaries live under /data/data// or /data/user///, and /proc/self/exe -// resolves there. -// -// A binary outside those directories has no package of its own, and that is not an edge case: a -// static build pushed to /data/local/tmp is how this runs on a device without Termux, and the Android -// 11 device reported "Cannot determine the package name of this process" for exactly that. The shell UID can -// be answered instead, because it has one name the service accepts. No other UID reaches here: an -// app's own binaries are always under its data directory, so a failure there is a real failure. -static bool getOwnPackage(char* buffer, size_t capacity) { - char path[4096]; - const ssize_t length = readlink("/proc/self/exe", path, sizeof(path) - 1); - if (length <= 0) { - FF_DEBUG("Cannot read /proc/self/exe, so the calling package cannot be derived"); - return false; - } - path[length] = '\0'; - - const char* rest = nullptr; - const char* const dataPrefix = "/data/data/"; - const char* const userPrefix = "/data/user/"; - if (strncmp(path, dataPrefix, strlen(dataPrefix)) == 0) { - rest = path + strlen(dataPrefix); - } else if (strncmp(path, userPrefix, strlen(userPrefix)) == 0) { - rest = strchr(path + strlen(userPrefix), '/'); - if (rest != nullptr) { - rest += 1; - } - } - if (rest == nullptr) { - const uint32_t uid = instance.state.platform.uid; - FF_DEBUG("The executable is not in an app data directory (\"%s\"), uid %u", path, uid); - if (uid != FF_ANDROID_PRIVILEGED_UID_SHELL) { - return false; - } - static const char shellPackage[] = FF_WIFI_ANDROID_SHELL_PACKAGE; - if (sizeof(shellPackage) > capacity) { - return false; - } - memcpy(buffer, shellPackage, sizeof(shellPackage)); - FF_DEBUG("The calling package is \"%s\" (the shell owns it)", buffer); - return true; - } - - const char* end = strchr(rest, '/'); - const size_t nameLength = end != nullptr ? (size_t) (end - rest) : strlen(rest); - if (nameLength == 0 || nameLength >= capacity) { - return false; - } - memcpy(buffer, rest, nameLength); - buffer[nameLength] = '\0'; - FF_DEBUG("The calling package is \"%s\"", buffer); - return true; -} +// The caller's own package name, which `getConnectionInfo` takes as its second argument: see +// ffAndroidGetOwnPackage() in common/android/package.h for why it has to be that one and how it is +// derived. `getWallpaper` in the Wallpaper module asks for the same thing. static bool isMacAddress(const char* value, uint32_t length) { if (length != FF_WIFI_ANDROID_MAC_STRING_LENGTH) { @@ -418,7 +360,7 @@ static void detectInterface(FFWifiAndroidConnection* connection) { } for (const struct ifaddrs* ifa = addrs; ifa != nullptr; ifa = ifa->ifa_next) { - if (ifa->ifa_name == nullptr || strncmp(ifa->ifa_name, "wlan", 4) != 0) { + if (ifa->ifa_name == nullptr || !ffStrStartsWith(ifa->ifa_name, "wlan")) { continue; } snprintf(connection->interface, sizeof(connection->interface), "%s", ifa->ifa_name); @@ -493,7 +435,7 @@ static void findStrings(const uint8_t* data, size_t size, size_t from, FFWifiAnd connection->bssidSeen = true; // The placeholder means there is no address to report: it is what the service writes // both for a caller it may not tell and for a connection that has none. - if (strcmp(buffer, FF_WIFI_ANDROID_DEFAULT_MAC_ADDRESS) != 0) { + if (!ffStrEquals(buffer, FF_WIFI_ANDROID_DEFAULT_MAC_ADDRESS)) { memcpy(connection->bssid, buffer, length + 1); } } else if (connection->state[0] == '\0' && isSupplicantState(buffer, length)) { @@ -804,7 +746,7 @@ static const char* parseConnectionInfo(const uint8_t* data, size_t size, FFWifiA // connection has no frequency to write, so one that is here and passed the checks above is a // connection. if (connection->state[0] != '\0') { - connection->connected = strcmp(connection->state, "COMPLETED") == 0; + connection->connected = ffStrEquals(connection->state, "COMPLETED"); } else { connection->connected = connection->ssidAbsent || ffBinderReadI32(data, size, FF_WIFI_ANDROID_OFF_NET_ID) >= 0; @@ -876,7 +818,7 @@ static const char* callIntMethod(FFBinder* binder, uint32_t handle, const char* uint8_t replyBuffer[64]; FFBinderReply reply = ffBinderReplyCreate(replyBuffer, sizeof(replyBuffer)); - error = ffBinderTransact(binder, handle, (uint32_t) transaction, &parcel, &reply); + error = ffBinderTransact(binder, handle, (uint32_t) transaction, 0, &parcel, &reply); if (error != nullptr) { return error; } @@ -894,22 +836,22 @@ static const char* callIntMethod(FFBinder* binder, uint32_t handle, const char* return nullptr; } -// `getOwnPackage` writes at most this many bytes, terminator included. +// `ffAndroidGetOwnPackage` writes at most this many bytes, terminator included. #define FF_WIFI_ANDROID_PACKAGE_SIZE 128 // The parcel for `getConnectionInfo` is the interface token, the package name and the -1 that // stands for a null `callingFeatureId`. A string16 costs 4 + 2 * (length + 1) bytes padded to four, // and the token puts three int32 in front of the descriptor. Deriving the size from the two names -// rather than rounding it up is what keeps the longest package name `getOwnPackage` can produce -// from marking the parcel truncated -- that fails the whole detection, rather than degrading to a -// call without a name. +// rather than rounding it up is what keeps the longest package name `ffAndroidGetOwnPackage` can +// produce from marking the parcel truncated -- that fails the whole detection, rather than degrading +// to a call without a name. #define FF_WIFI_ANDROID_PARCEL_SIZE \ (12 + ((4 + 2 * sizeof(FF_WIFI_ANDROID_DESCRIPTOR) + 3) & ~3u) \ + ((4 + 2 * FF_WIFI_ANDROID_PACKAGE_SIZE + 3) & ~3u) + 4) static const char* detectWithBinder(FFlist* result) { char package[FF_WIFI_ANDROID_PACKAGE_SIZE]; - if (!getOwnPackage(package, sizeof(package))) { + if (!ffAndroidGetOwnPackage(package, sizeof(package))) { return "Cannot determine the package name of this process"; } @@ -945,7 +887,7 @@ static const char* detectWithBinder(FFlist* result) { uint8_t replyBuffer[FF_WIFI_ANDROID_REPLY_SIZE]; FFBinderReply reply = ffBinderReplyCreate(replyBuffer, sizeof(replyBuffer)); - error = ffBinderTransact(&binder, service.handle, (uint32_t) transaction, &parcel, &reply); + error = ffBinderTransact(&binder, service.handle, (uint32_t) transaction, 0, &parcel, &reply); if (error != nullptr) { return error; }