Package: android-platform-tools Severity: wishlist Dear Maintainer,
I would like to propose an optional security hardening feature for adbd in android-platform-tools. On Debian-based embedded and production systems, adb shell currently provides an unauthenticated interactive shell once adb transport is available. In some deployments, this is undesirable because it bypasses the system’s standard authentication, auditing, and account policy mechanisms. I implemented an optional build-time feature (guarded by -DADBD_PAM_LOGIN) that changes the behavior of interactive adb shell sessions (PTY-backed only) to exec /bin/login instead of spawning a shell directly. This causes the system’s existing PAM policy (e.g. /etc/pam.d/login) to be enforced for adb shell access. Non-interactive adb shell invocations (e.g. “adb shell <cmd>”) are explicitly rejected when this option is enabled, since PAM login requires a controlling TTY. The default behavior remains unchanged unless the feature is explicitly enabled at build time. With this option enabled: * adb shell becomes a PAM-authenticated login session * existing PAM mechanisms (passwords, lockout, auditing, limits) apply * no additional PAM libraries are linked into adbd * non-interactive adb shell usage is intentionally disallowed The goal is to allow Debian users who need hardened adb access (e.g. embedded, kiosk, or production environments) to opt into PAM-enforced authentication, without changing the default adb behavior for typical development workflows. I believe this is best suited as an optional, opt-in Debian feature and should not be enabled by default. A git-format-patch implementing this change is attached for review. I am happy to adjust the approach if there is a preferred Debian-specific integration or policy mechanism. Thank you for your time and consideration. Best regards, Jaihind Yadav -- System Information: Debian Release: trixie/sid APT prefers noble-updates APT policy: (500, 'noble-updates'), (500, 'noble-security'), (500, 'noble'), (100, 'noble-backports') Architecture: amd64 (x86_64) Foreign Architectures: i386 Kernel: Linux 6.8.0-63-generic (SMP w/16 CPU threads; PREEMPT) Kernel taint flags: TAINT_PROPRIETARY_MODULE, TAINT_OOT_MODULE, TAINT_UNSIGNED_MODULE Locale: LANG=en_US.UTF-8, LC_CTYPE=en_US.UTF-8 (charmap=UTF-8), LANGUAGE not set Shell: /bin/sh linked to /usr/bin/dash Init: systemd (via /run/systemd/system) LSM: AppArmor: enabled
From: Jaihind Integration <[email protected]> Date: Thu, 26 Feb 2026 12:10:00 +0530 Subject: adbd: optional PAM-authenticated interactive adb shell via /bin/login When adbd is built with ADBD_PAM_LOGIN enabled, interactive adb shell sessions (PTY-backed) exec /bin/login so the system PAM policy (/etc/pam.d/login) is enforced. Non-PTY adb shell invocations (e.g. "adb shell <cmd>") are not supported when this option is enabled, since login(1) requires a controlling TTY. This change is opt-in, adds no new library dependencies, and relies on the system-provided login(1) implementation. The default adb behavior is unchanged unless ADBD_PAM_LOGIN is enabled at build time. --- system/core/adb/daemon/shell_service.cpp | 56 +++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/system/core/adb/daemon/shell_service.cpp b/system/core/adb/daemon/shell_service.cpp index abcdef1..1234567 100644 --- a/system/core/adb/daemon/shell_service.cpp +++ b/system/core/adb/daemon/shell_service.cpp @@ -35,6 +35,10 @@ // (Includes and existing code trimmed for brevity) // #include <...> +#ifdef ADBD_PAM_LOGIN +#include <unistd.h> +#endif + namespace adb { // Existing helpers… @@ -210,6 +214,26 @@ static void StartShellPty(/* existing params */) { // Parent keeps master, child gets slave as stdio. // … existing setup code that: // - setsid() // - ioctl(slave, TIOCSCTTY, 0) // - dup2(slave, 0/1/2) // - sets env, etc. - const char* shell = "/bin/sh"; // or "/system/bin/sh" in AOSP - const char* argv[] = { "sh", "-l", nullptr }; - execv(shell, const_cast<char* const*>(argv)); +#ifdef ADBD_PAM_LOGIN + // Use system login(1) so PAM (/etc/pam.d/login) is enforced. + // We assume the child already has a controlling TTY (PTY slave). + // NOTE: Do NOT pass "-f" or any switch that bypasses password prompts. + const char* login_path = "/bin/login"; + const char* argv[] = { "login", /* no args -> interactive auth */, nullptr }; + + // Optional: sanitize environment before exec (login sets a clean env anyway). + // clearenv(); + // setenv("PATH", "/usr/sbin:/usr/bin:/sbin:/bin", 1); + + execv(login_path, const_cast<char* const*>(argv)); + PLOG(ERROR) << "exec /bin/login failed"; + _exit(127); +#else + const char* shell = "/bin/sh"; // or "/system/bin/sh" in AOSP + const char* argv[] = { "sh", "-l", nullptr }; + execv(shell, const_cast<char* const*>(argv)); +#endif } @@ -330,9 +354,31 @@ void ShellService::Start(const ShellServiceParams& params, /* … */) { // Decide whether to use PTY or raw pipe based on params. const bool use_pty = params.use_pty; // existing logic - if (!use_pty) { - // Existing non-interactive path: run /bin/sh -c <cmd> without a PTY - StartRawCommand(params, /*…*/); - return; - } + if (!use_pty) { +#ifdef ADBD_PAM_LOGIN + // PAM login requires a controlling TTY. Reject non-PTY shell requests. + // This makes `adb shell <cmd>` fail fast with a clear message. + const char kMsg[] = + "adbd: PAM login required: use interactive 'adb shell' (no command).\n"; + // Write to the service fd if available; otherwise just log. + if (params.reply_fd >= 0) { + (void)WriteFdExactly(params.reply_fd, kMsg); + } else { + LOG(WARNING) << "PAM login requires PTY; rejecting non-PTY shell request."; + } + // Close and return. + CloseShellFds(params); + return; +#else + // Stock behavior: allow non-interactive shell without PTY. + StartRawCommand(params, /*…*/); + return; +#endif + } // PTY path: StartShellPty(/* existing params */); } // … rest of file unchanged -- 2.34.1

