Author: admin

  • eBPF Lab: Building a Custom Android Kernel Event Monitor (Hands-On Tutorial)

    Introduction to eBPF on Android

    The Extended Berkeley Packet Filter (eBPF) has revolutionized kernel-level tracing, networking, and security across various Linux distributions. Its ability to run sandboxed programs directly within the kernel, triggered by various events, offers unparalleled introspection and performance. While eBPF’s adoption is widespread in server environments, its integration and utilization within the Android ecosystem present unique challenges and opportunities. This hands-on tutorial guides you through building a custom Android kernel with eBPF support and deploying a simple event monitor.

    Android, being a heavily customized Linux distribution, requires specific considerations for eBPF development. Key challenges include dealing with a diverse range of kernel versions, often outdated eBPF toolchains, and strict security policies like SELinux. However, the insights gained from direct kernel monitoring using eBPF can be invaluable for performance optimization, security analysis, and debugging complex system behaviors on Android devices.

    Setting Up the Android Build Environment

    Before we can build our custom kernel, we need a complete Android Open Source Project (AOSP) build environment. This ensures we have the correct toolchains, headers, and build scripts. For this tutorial, we’ll assume a standard Linux development machine (Ubuntu 20.04+ recommended).

    1. Syncing AOSP Source

    First, set up your AOSP environment. Choose a stable branch (e.g., Android 12 or 13) that typically offers better eBPF support. This process can take several hours depending on your internet connection.

    mkdir -p ~/android/aosp
    cd ~/android/aosp
    repo init -u https://android.googlesource.com/platform/manifest -b android-13.0.0_r40
    repo sync -j8

    2. Identifying Your Device’s Kernel Source

    Android kernels are often prebuilt and device-specific. You’ll need the kernel source that matches your target device (or a Generic Kernel Image, GKI, if applicable). The kernel source for AOSP builds is typically located under `aosp/kernel/common` or `aosp/device/manufacturer/device-name/kernel`. For simplicity, we’ll work with `kernel/common` and assume a `gki_arm64` configuration.

    cd ~/android/aosp/kernel/common

    Enabling eBPF in the Kernel Configuration

    Most modern Android kernels support eBPF, but it might not be fully enabled or configured for your specific needs. We need to modify the kernel `.config` file.

    1. Locating the Kernel Configuration

    The kernel configuration files are usually found in `arch/arm64/configs/` (for ARM64 devices). We’ll modify a common GKI configuration. For example:

    cp arch/arm64/configs/gki_arm64_defconfig .config
    make menuconfig

    During `make menuconfig`, navigate and enable the following options. Use the search function (`/`) to find them quickly:

    • CONFIG_BPF_SYSCALL: Enable eBPF system call.
    • CONFIG_BPF_JIT: Enable eBPF JIT compiler (for performance).
    • CONFIG_BPF_EVENTS: Enable kprobes/uprobes, tracepoints for eBPF.
    • CONFIG_DEBUG_INFO_BTF: Enable BTF (BPF Type Format) for richer debugging info.
    • CONFIG_KPROBE_EVENTS, CONFIG_UPROBE_EVENTS, CONFIG_FTRACE, CONFIG_FUNCTION_GRAPH_TRACER: Essential tracing infrastructure.

    After saving your changes, the `.config` file will be updated.

    Compiling a Custom Android Kernel

    With the eBPF options enabled, we can now compile our custom kernel. Ensure you have the AOSP prebuilts toolchain in your PATH.

    1. Setting Up Build Variables

    export ARCH=arm64
    export CROSS_COMPILE=aarch64-linux-android-
    export PATH=~/android/aosp/prebuilts/clang/host/linux-x86/clang-r450784d/bin/:$PATH # Adjust path to your AOSP clang toolchain
    export LLVM=1 # Use LLVM for kernel build

    2. Building the Kernel

    Now, build the kernel image and modules:

    make -j$(nproc)

    Upon successful compilation, you should find your kernel image (e.g., `Image.gz`) in `arch/arm64/boot/` and potentially a `boot.img` or `init_boot.img` if you follow device-specific build instructions. This tutorial assumes you’ll integrate this kernel into a full AOSP build or flash it separately.

    Developing an eBPF Program: Monitoring `sys_enter`

    Let’s create a simple eBPF program that monitors every system call entry (`sys_enter`) and records the system call ID and process ID into a BPF map. We’ll write this in C and compile it using `clang` with eBPF targets.

    1. eBPF C Program (`syscall_monitor.bpf.c`)

    #include <vmlinux.h>
    #include <bpf/bpf_helpers.h>
    #include <bpf/bpf_tracing.h>
    
    // Define a BPF map to store syscall counts
    struct {
        __uint(type, BPF_MAP_TYPE_HASH);
        __uint(max_entries, 1024);
        __type(key, u32);
        __type(value, u64);
    } syscall_counts SEC(".maps");
    
    // Define a BPF map to store syscall event logs
    struct syscall_event {
        u32 pid;
        u32 syscall_id;
    };
    
    struct {
        __uint(type, BPF_MAP_TYPE_RINGBUF);
        __uint(max_entries, 256 * 1024); // 256 KB buffer
    } events SEC(".maps");
    
    // Tracepoint handler for sys_enter
    SEC("tp/raw_syscalls/sys_enter")
    int handle_sys_enter(struct bpf_raw_tracepoint_args *ctx)
    {
        u32 syscall_id = ctx->args[1];
        u64 pid_tgid = bpf_get_current_pid_tgid();
        u32 pid = pid_tgid >> 32; // Get PID from pid_tgid
    
        // Increment syscall count
        u64 *count = bpf_map_lookup_elem(&syscall_counts, &syscall_id);
        if (count) {
            *count += 1;
        } else {
            u64 init_count = 1;
            bpf_map_update_elem(&syscall_counts, &syscall_id, &init_count, BPF_NOEXIST);
        }
    
        // Log event to ring buffer
        struct syscall_event *event;
        event = bpf_ringbuf_reserve(&events, sizeof(struct syscall_event), 0);
        if (event) {
            event->pid = pid;
            event->syscall_id = syscall_id;
            bpf_ringbuf_submit(event, 0);
        }
    
        return 0;
    }
    
    char LICENSE[] SEC("license") = "GPL";

    2. Compiling the eBPF Program

    You’ll need `clang` with eBPF support and `libbpf` headers. Use the AOSP clang toolchain you set up earlier.

    clang -target bpf -O2 -emit-llvm -c syscall_monitor.bpf.c -o - | llc -march=bpf -filetype=obj -o syscall_monitor.bpf.o
    # Alternatively, if bpf-clang supports it directly:
    clang -target bpf -g -O2 -c syscall_monitor.bpf.c -o syscall_monitor.bpf.o

    Loading and Attaching the eBPF Program

    On the Android device (after flashing your custom kernel), you’ll need a userspace loader to load the `syscall_monitor.bpf.o` file, create the BPF maps, and attach the program to the `sys_enter` tracepoint.

    You can use `libbpf` from a custom Android application or leverage `bpftool` if it’s available or compiled for your Android userspace. For a quick test, we’ll demonstrate using `bpftool` commands (assuming it’s available).

    1. Pushing the eBPF Object File to Device

    adb push syscall_monitor.bpf.o /data/local/tmp/

    2. Loading and Attaching with `bpftool` (on device)

    Ensure `bpftool` (compiled for Android’s userspace) is available on your device, perhaps by pushing it to `/data/local/tmp/`.

    adb shell
    cd /data/local/tmp/
    # Load the program and maps
    bpftool prog load syscall_monitor.bpf.o /sys/fs/bpf/syscall_monitor
    
    # Attach to the tracepoint
    bpftool net attach tp raw_syscalls/sys_enter prog_id 
    # To find PROG_ID: bpftool prog show -> look for ID of syscall_monitor

    Note: Attaching to tracepoints usually requires root privileges. SELinux policies on Android might also prevent this. You might need to adjust SELinux or temporarily put it in permissive mode during development (`setenforce 0`).

    Reading eBPF Map Data from Userspace

    Once the eBPF program is running, we can read the data from the maps. The `syscall_counts` map holds aggregated data, and the `events` ring buffer provides a stream of individual events.

    1. Reading `syscall_counts` Map

    # On device (root required)
    bpftool map dump id 
    # To find MAP_ID_SYSCALL_COUNTS: bpftool map show -> look for ID of syscall_counts

    This will output key-value pairs of syscall IDs and their counts.

    2. Reading `events` Ring Buffer

    Reading from the ring buffer is slightly more complex. You’d typically use a `libbpf` userspace application to poll the ring buffer for new events. Here’s a conceptual outline of what that C code would do:

    #include <stdio.h>
    #include <stdlib.h>
    #include <signal.h>
    #include <unistd.h>
    #include <errno.h>
    #include <string.h>
    #include <bpf/libbpf.h>
    #include "syscall_monitor.bpf.h" // Header generated by bpftool for map definitions
    
    static volatile bool exiting = false;
    
    void sig_handler(int sig) {
        exiting = true;
    }
    
    int handle_event(void *ctx, void *data, size_t data_sz)
    {
        const struct syscall_event *e = data;
        printf("PID: %u, Syscall ID: %un", e->pid, e->syscall_id);
        return 0;
    }
    
    int main(int argc, char **argv)
    {
        struct bpf_object *obj;
        struct bpf_map *events_map;
        struct perf_buffer *pb = NULL;
    
        signal(SIGINT, sig_handler);
        signal(SIGTERM, sig_handler);
    
        // Assume bpf_object__open_file and bpf_object__load_file logic here
        // For simplicity, directly open the BPF FS map
    
        int map_fd = bpf_obj_get("/sys/fs/bpf/events"); // Path to your ring buffer map
        if (map_fd < 0) {
            fprintf(stderr, "ERROR: Failed to open BPF map /sys/fs/bpf/events: %sn", strerror(errno));
            return 1;
        }
    
        pb = perf_buffer__new(map_fd, 64, handle_event, NULL, NULL);
        if (!pb) {
            fprintf(stderr, "ERROR: Failed to create perf buffer: %sn", strerror(errno));
            close(map_fd);
            return 1;
        }
    
        printf("Monitoring syscall events... Press Ctrl-C to stop.n");
        while (!exiting) {
            perf_buffer__poll(pb, 100); // Poll every 100ms
        }
    
        perf_buffer__free(pb);
        close(map_fd);
        return 0;
    }

    This userspace program (which needs to be compiled for Android) would attach to the `events` map (assumed to be pinned to `/sys/fs/bpf/events`) and continuously print incoming syscall events. Building such an application for Android would involve cross-compiling with the NDK and linking against `libbpf`.

    Deployment Considerations and Next Steps

    Deploying a custom kernel with eBPF on an Android device is a complex task. You’ll typically replace the `boot.img` (containing the kernel and ramdisk) for older devices or `init_boot.img` for newer GKI-enabled devices. Always ensure you have a way to recover your device (e.g., fastboot access) before flashing custom kernels.

    Further enhancements could include:

    • **Conditional Tracing**: Only trace specific PIDs or syscalls.
    • **Data Filtering**: Filter events in the eBPF program to reduce userspace overhead.
    • **Advanced Maps**: Use `BPF_MAP_TYPE_STACK_TRACE` to capture call stacks.
    • **User-friendly Interface**: Develop a dedicated Android application that loads eBPF programs, configures them, and presents the traced data in an intuitive manner.
    • **SELinux Policy**: Craft specific SELinux policies to allow eBPF operations without disabling the entire security framework.

    eBPF opens up a powerful new dimension for understanding and optimizing Android’s core system behavior. While the setup requires expertise in kernel compilation and a deep understanding of the Android platform, the rewards in terms of debuggability and performance insights are substantial.

  • Case Study: Fixing a Tricky Android SELinux Denial in a Custom System App

    Introduction: Navigating Android’s Security Enforcer

    Android’s security model is robust, and a cornerstone of this strength is Security-Enhanced Linux (SELinux). Introduced in Android 4.3, SELinux operates in enforcing mode across all current Android devices, providing mandatory access control (MAC) over all processes, files, and IPC mechanisms. While it significantly enhances security, it often presents a steep learning curve for developers and system integrators building custom Android distributions or system-level applications.

    This case study delves into the practical challenges of debugging and resolving a common yet intricate SELinux denial encountered when integrating a custom system application. We’ll walk through the process of identifying a denial, understanding its components, crafting targeted SELinux policies, and iteratively refining them to achieve a fully functional and secure system.

    Dissecting Android’s SELinux Architecture

    Before diving into the denial, it’s crucial to grasp the fundamental concepts of SELinux on Android:

    • Contexts: Every process, file, and system object has an associated security context, typically in the format user:role:type:level. The ‘type’ field is the most relevant for policy decisions.
    • Domains: A special type assigned to processes. It defines what a process is allowed to do.
    • Types: Labels for files and objects, defining their characteristics and permissions.
    • Classes: Categories of system objects (e.g., file, dir, chr_file, socket, process).
    • Permissions: Actions that can be performed on an object (e.g., read, write, open, ioctl).
    • Policy Files (`.te`): Text files (`.te` for Type Enforcement) defining rules for types, domains, and their interactions. These are compiled into a binary policy (`sepolicy.cil`).

    The core principle is that access is denied by default; explicit rules must be present to grant any permission.

    The Case Study: A Custom System App’s Struggle

    Imagine we’re developing a custom Android system app, com.example.mydeviceservice, designed to interact with a proprietary kernel driver. This driver exposes a character device node at /dev/my_sensor_dev. Our app, intended to run as a privileged system component, starts and immediately crashes or fails to perform its core function: communicating with the sensor via its device node.

    Step 1: Identifying the SELinux Denial

    The first step in any SELinux debugging scenario is to find the denial messages. These typically appear in the kernel log buffer (`dmesg`) or `logcat` if the denial involves an Android process.

    Gathering Denial Logs

    Connect your Android device via ADB and run:

    adb shell dmesg | grep

  • Securing Android System Apps: Crafting Minimal SELinux Policies for Zero Trust

    Introduction: The Imperative of SELinux in Android Zero Trust

    In the evolving landscape of mobile security, Android’s robust Security-Enhanced Linux (SELinux) framework stands as a critical pillar, enforcing Mandatory Access Control (MAC) policies across the operating system. For system application developers and custom ROM builders, understanding and correctly implementing SELinux policies is not merely a best practice; it’s fundamental to achieving a Zero Trust security posture. A Zero Trust model dictates that no entity, inside or outside the network perimeter, is inherently trusted. On Android, SELinux policies translate this principle into granular access controls, ensuring that every interaction between processes, files, and resources is explicitly permitted.

    This article delves into the advanced techniques of crafting minimal SELinux policies for Android system applications. We’ll explore why minimalism is key, how to effectively debug denials, and how to build policies that restrict privileges to only what is absolutely necessary, thereby significantly reducing the attack surface and enhancing overall system integrity.

    Why Minimal Policies? The Zero Trust Advantage

    The philosophy behind minimal SELinux policies is simple: grant only the permissions a component absolutely requires to function, and no more. This principle directly aligns with Zero Trust. Overly broad or permissive policies introduce unnecessary attack vectors. For instance, allowing a system service to write to arbitrary system files or execute any process type dramatically increases the blast radius if that service is compromised.

    • Reduced Attack Surface: Less permissible policies mean fewer ways for an attacker to leverage a compromised process.
    • Improved System Stability: Prevents legitimate, but buggy, applications from performing unintended operations.
    • Enhanced Auditability: Clearer policies make it easier to understand and audit the security posture of individual components.
    • Easier Debugging: Specific denials point directly to missing permissions for specific actions.

    Understanding Android’s SELinux Contexts

    Android’s SELinux implementation uses specific types to label files, processes, and IPC mechanisms. Key contexts you’ll encounter include:

    • system_app: For applications signed with the platform key, typically pre-installed.
    • platform_app: Similar to system_app but with slightly different privileges.
    • untrusted_app: For user-installed applications downloaded from app stores.
    • zygote: The initial process that forks to create app processes.
    • system_server: The central process for many system services.
    • init: The first process started at boot, responsible for launching other services.

    Your custom system application will likely require its own unique domain and associated types to ensure isolation and precise control.

    The SELinux Policy Development Workflow: A Step-by-Step Guide

    Crafting effective SELinux policies involves a systematic approach, often starting with observation and incrementally building up permissions.

    Step 1: Start in Permissive Mode (for Development)

    During initial development, setting SELinux to permissive mode allows you to observe denials without blocking operations. This is crucial for identifying all necessary permissions. Never deploy a device with SELinux in permissive mode.

    adb shell su -c 'setenforce 0'

    Step 2: Trigger Denials and Capture Logs

    Run your system app and exercise all its functionalities. Simultaneously, capture SELinux denial messages from the kernel ring buffer and logcat.

    # In one terminal: Tail kernel messages for AVC denials adb shell su -c 'dmesg -w | grep avc' # In another terminal: Monitor logcat for AVC denials adb logcat | grep 'avc: denied'

    An `avc: denied` message typically looks like this:

    avc: denied { read } for pid=1234 comm=

  • Live SELinux Debugging on Android: Using `sepolicy-inject` and `dmesg` for Instant Fixes

    Introduction: Navigating the Android SELinux Labyrinth

    SELinux (Security-Enhanced Linux) is a mandatory access control (MAC) system that provides a robust security layer on Android devices. While it significantly enhances device security by confining processes and controlling access to resources, it often presents a formidable challenge for developers and custom ROM builders. Policy denials, often manifested as unexpected application crashes, permission errors, or device malfunctions, can be notoriously difficult to debug, especially when dealing with complex system interactions.

    Traditionally, fixing SELinux denials involved a tedious cycle: identify the denial, modify the SELinux policy source, recompile the entire Android system or boot image, flash the device, and then test. This iteration loop is incredibly time-consuming. Fortunately, tools like `sepolicy-inject` combined with real-time kernel logging via `dmesg` offer a powerful, live debugging methodology, allowing for immediate policy rule injection and verification without a full system reboot or reflash. This article will guide you through an expert-level approach to debugging SELinux denials directly on a running Android device.

    Understanding SELinux AVC Denials

    At the heart of SELinux debugging are Access Vector Cache (AVC) denials. These occur when an operation requested by a subject (a process or application) on an object (a file, socket, binder service, etc.) is not explicitly permitted by the active SELinux policy. When an AVC denial happens, the Linux kernel logs a specific message. You can typically find these messages using `dmesg` or `logcat`.

    Anatomy of an AVC Denial:

    avc: denied {  } for pid= comm="" scontext= tcontext= tclass= permissive=0
    • `permission`: The specific action that was denied (e.g., `read`, `write`, `getattr`, `call`).
    • `pid`/`comm`: The process ID and name of the subject attempting the action.
    • `scontext`: The SELinux context of the subject (e.g., `u:r:untrusted_app:s0`).
    • `tcontext`: The SELinux context of the target object (e.g., `u:object_r:device:s0`).
    • `tclass`: The class of the target object (e.g., `file`, `binder`, `socket`).
    • `permissive`: Indicates if the domain is in permissive mode (0 for enforcing, 1 for permissive).

    Our goal is to derive an `allow` rule from this denial that grants the specific permission, effectively bypassing the block.

    Setting Up Your Live Debugging Environment

    To follow this guide, you’ll need:

    1. A rooted Android device. Root access is mandatory for modifying SELinux policy live.
    2. ADB (Android Debug Bridge) installed and configured on your host machine.
    3. The `sepolicy-inject` utility. This tool is part of AOSP and can be built from source or downloaded. It needs to be pushed to your device.

    Obtaining and Pushing `sepolicy-inject`

    If building from AOSP, `sepolicy-inject` is usually found at `out/host/linux-x86/bin/sepolicy-inject` (or similar for other host OS). Alternatively, you can find precompiled binaries for your device’s architecture.

    # Assuming you have an ARM64 device and 'sepolicy-inject' binary is in current directory
    adb push sepolicy-inject /data/local/tmp/
    adb shell "chmod 755 /data/local/tmp/sepolicy-inject"

    Now you can execute `sepolicy-inject` from your device’s shell.

    The `dmesg` to `sepolicy-inject` Workflow

    This workflow outlines the iterative process for identifying and resolving SELinux denials in real-time.

    Step 1: Identify the Denial Using `dmesg`

    First, clear the kernel log to get fresh output, then trigger the action that causes the denial, and immediately check `dmesg`.

    adb shell
    su
    dmesg -c # Clears kernel ring buffer
    # Now, on the device, try to perform the action that fails (e.g., launch app, access file)
    # Back in the adb shell:
    dmesg | grep avc

    Let’s assume you get an output similar to this:

    avc: denied { call } for pid=1234 comm="my_service" scontext=u:r:my_app:s0 tcontext=u:r:system_server:s0 tclass=binder permissive=0

    Step 2: Crafting the Policy Rule from the Denial

    From the example denial, we can extract the necessary components to form an `allow` rule:

    • `scontext`: `my_app` (or more precisely, `u:r:my_app:s0`)
    • `tcontext`: `system_server` (or `u:r:system_server:s0`)
    • `tclass`: `binder`
    • `permission`: `call`

    The corresponding SELinux policy rule would be:

    allow my_app system_server:binder call;

    Step 3: Injecting the Rule with `sepolicy-inject`

    The `sepolicy-inject` tool takes these components and dynamically applies them to the live kernel policy. The current active policy is typically found at `/sys/fs/selinux/policy`.

    adb shell
    su
    /data/local/tmp/sepolicy-inject -s my_app -t system_server -c binder -p call -P /sys/fs/selinux/policy

    Let’s break down the command:

    • `-s `: Specifies the source context type (e.g., `my_app`).
    • `-t `: Specifies the target context type (e.g., `system_server`).
    • `-c `: Specifies the target object class (e.g., `binder`).
    • `-p `: Specifies the permission being granted (e.g., `call`).
    • `-P /sys/fs/selinux/policy`: Specifies the path to the live SELinux policy file in the kernel. This is crucial for applying changes immediately.

    A successful injection will usually return silently or indicate success. If there’s an error, `sepolicy-inject` will print diagnostics.

    Step 4: Verify and Iterate

    After injecting the rule, immediately re-attempt the action that previously caused the denial. Check `dmesg` again:

    dmesg -c
    # Re-attempt the problematic action
    dmesg | grep avc

    If the original denial is gone, congratulations! The immediate issue is resolved. However, it’s common for one denial to mask others. You might encounter a new AVC denial related to the same action, or a different one later. In such cases, repeat steps 1-3 until all related denials are addressed.

    Advanced Scenarios and Best Practices

    Dealing with Multiple Denials

    Sometimes a single action can trigger several AVC denials simultaneously or in rapid succession. Address them one by one, starting from the earliest denial logged. Each successful injection may reveal the next underlying denial.

    Temporary vs. Permanent Fixes

    Rules injected via `sepolicy-inject` are temporary. They only persist until the next device reboot. For a permanent solution, you must modify the actual SELinux policy source (`.te` files in AOSP) and rebuild/flash your boot image. `sepolicy-inject` is a powerful prototyping tool, but not a deployment solution.

    Security Implications of Broad Rules

    While `sepolicy-inject` allows for rapid debugging, be mindful of the rules you inject. Overly broad `allow` rules (e.g., `allow my_app *:file *;`) can significantly weaken your device’s security posture. Always aim for the principle of least privilege: grant only the specific permission for the specific interaction required.

    When `sepolicy-inject` Isn’t Enough

    For more complex policy issues, especially those involving type transitions, type enforcement for new services, or more intricate labeling requirements, `sepolicy-inject` might not be sufficient. In those scenarios, a deeper dive into SELinux policy language and a full rebuild cycle are unavoidable. However, for quick `allow` rule testing, it remains unparalleled.

    Conclusion

    Live SELinux debugging on Android with `sepolicy-inject` and `dmesg` revolutionizes the development and customization workflow. By providing an instant feedback loop, it drastically cuts down the time spent debugging policy denials, allowing developers to quickly prototype and validate SELinux rules. While these changes are temporary, the ability to rapidly iterate on policy adjustments is invaluable for identifying root causes and crafting robust, secure, and permanent SELinux policies for Android projects. Master this technique, and you’ll navigate the complexities of Android security with unprecedented agility.

  • Automated SELinux Policy Generation for Android AOSP Builds: A Scripting Tutorial

    Introduction

    SELinux (Security-Enhanced Linux) is a mandatory access control (MAC) system that provides a robust security layer for Android. In Android Open Source Project (AOSP) builds, SELinux policies are critical for defining what processes can access which resources, effectively sandboxing components and mitigating vulnerabilities. However, writing and debugging SELinux policies, especially for custom services and hardware abstractions (HALs), can be a complex and time-consuming task, often involving an iterative cycle of generating AVC (Access Vector Cache) denials, interpreting them, and manually crafting policy rules. This tutorial will guide you through a scripting-based approach to automate the analysis of AVC denials and suggest SELinux policy rules, significantly streamlining the development process for your AOSP customizations.

    Understanding SELinux in Android AOSP

    Android utilizes SELinux in enforcing mode, meaning all access attempts are checked against the loaded policy. If no explicit rule permits an action, it is denied. Key concepts include:

    • Enforcing vs. Permissive: In enforcing mode, denials block operations. In permissive mode, denials are logged but operations are allowed, useful for initial debugging. Android aims for enforcing mode everywhere.
    • Types and Domains: Everything in SELinux has a type: processes run in domains (a special type for processes), and files, sockets, and other resources have object types. Policies define interactions between these types. For example, a process running in the `my_service` domain might need to access a file with `data_file_type`.
    • Rules and Statements: Policy rules, defined in .te (type enforcement) files, specify allowed interactions. Common rules include allow, neverallow, dontaudit, and various attribute declarations.

    The Challenge of Manual SELinux Policy Creation

    When developing new Android features or integrating third-party components, it’s common to encounter AVC denials during runtime. These denials indicate that a process is attempting an action not permitted by the current SELinux policy. Manually addressing these involves:

    1. Identifying the denial in the device logs.
    2. Interpreting the denial to understand the source (scontext), target (tcontext), target class (tclass), and requested permissions.
    3. Writing the corresponding allow rules in the appropriate .te file.
    4. Rebuilding the SELinux policy and flashing the device.
    5. Repeating until all necessary permissions are granted without introducing excessive privileges.

    This iterative process is tedious and prone to errors, especially when dealing with a large number of denials.

    Collecting and Analyzing AVC Denials

    Identifying Denials

    The first step in automating policy generation is to reliably capture AVC denials. These denials are typically logged to the kernel ring buffer, accessible via dmesg or logcat. When an AVC denial occurs, it usually contains critical information in a specific format.

    To view real-time denials:

    adb shell dmesg -wH | grep

  • Cracking SELinux AVC Denials on Android: The Ultimate Debugging Toolkit

    Introduction: Navigating Android’s SELinux Fortress

    Android’s security model is robust, and at its core lies SELinux (Security-Enhanced Linux). Since Android 5.0 Lollipop, SELinux operates in enforcing mode, mandating that every process, file, and system resource interaction adhere to a strict policy. While this drastically enhances security by limiting the damage potential of compromised processes, it presents a unique challenge for developers and custom ROM builders: SELinux AVC (Access Vector Cache) denials. These denials occur when an attempted operation violates the defined policy, leading to unexpected application crashes, system instability, or features not working as intended. Mastering the art of debugging these denials is paramount for anyone venturing into advanced Android customization, system development, or security hardening.

    This article provides an expert-level guide to understanding, identifying, and resolving SELinux AVC denials on Android. We’ll explore essential on-device tools and a systematic workflow to transform policy writing from a frustrating guessing game into a precise, data-driven process.

    Deconstructing AVC Denials: The Anatomy of a Security Block

    An AVC denial message is the kernel’s way of telling you that an action was blocked. Understanding its components is the first step to crafting a solution. A typical denial message looks like this:

    avc: denied { read } for pid=1234 comm="my_service" name="target_file" dev="dm-0" ino=5678 scontext=u:r:my_service:s0 tcontext=u:object_r:some_file_type:s0 tclass=file permissive=0

    Let’s break down the critical elements:

    • avc: denied { <permission> }: The specific permission that was denied (e.g., read, write, execute, transfer).
    • pid=<ID> comm="<command>": The Process ID and the name of the command/process attempting the action.
    • name="<target_name>": The name of the file or resource being accessed.
    • scontext=<source_context>: The Security Context of the subject (the process trying to perform the action). This usually follows the format u:r:<domain_type>:s0, where <domain_type> is the crucial part for policy writing.
    • tcontext=<target_context>: The Security Context of the target (the resource being accessed). This usually follows u:object_r:<type>:s0, where <type> is the target type.
    • tclass=<target_class>: The class of the target object (e.g., file, dir, socket, binder, service_manager).

    The goal is to write a policy rule that specifically allows scontext to perform permission on tclass objects labeled with tcontext.

    Essential Tools for On-Device Debugging

    While AOSP provides audit2allow for generating policy rules from logs, on a running Android device, you need more dynamic tools.

    1. The dmesg & logcat Duo

    The primary source for AVC denials is the kernel ring buffer, accessible via dmesg. logcat also mirrors these messages, but dmesg often provides them in a more raw, unfiltered form.

    To capture current denials:

    adb shell dmesg | grep 'avc: denied'

    To capture denials after a specific action, clear the buffer first, trigger the action, then retrieve:

    adb shell dmesg -c # Clears the kernel ring buffer
    # Perform the action that causes the denial (e.g., start an app, run a service)
    adb shell dmesg | grep 'avc: denied'

    Alternatively, filter logcat:

    adb logcat | grep 'avc: denied'

    2. sepolicy-inject: Dynamic Policy Testing

    sepolicy-inject is an invaluable tool for on-device policy prototyping. It allows you to inject temporary SELinux rules into the running kernel’s policy. This is critical for quickly validating policy changes without needing to recompile and flash your device repeatedly.

    First, obtain the sepolicy-inject binary (often found in AOSP source under external/sepolicy/tools or built as part of the Android `m` build output) and push it to your device:

    adb push path/to/sepolicy-inject /data/local/tmp/

    Then, use it to inject a rule. For instance, to allow my_service domain to read some_file_type files:

    adb shell /data/local/tmp/sepolicy-inject -s my_service -t some_file_type -c file -p read -P /sys/fs/selinux/policy

    The -P /sys/fs/selinux/policy argument specifies the policy to modify. After injection, re-trigger the action to see if the denial is resolved. These changes are temporary and will revert upon reboot.

    3. sesearch: Unraveling Existing Policy

    Before adding a new rule, it’s often helpful to understand what rules already exist for a given subject, object, or class. sesearch helps query the loaded SELinux policy.

    Push sesearch (also from AOSP tools) to your device:

    adb push path/to/sesearch /data/local/tmp/

    Example usage: Find all permissions that my_service has on some_file_type:

    adb shell /data/local/tmp/sesearch -s my_service -t some_file_type -c file -A

    The -A flag shows all rules. You can also specify a particular permission with -p <permission>.

    A Step-by-Step Debugging Workflow

    Here’s a systematic approach to resolving AVC denials:

    Step 1: Identify the Denial

    Start by clearing your kernel log buffer and triggering the problematic action. This isolates the relevant denial message(s).

    adb shell dmesg -c
    # Perform the action that fails
    adb shell dmesg | grep 'avc: denied' -C 5 # -C 5 provides 5 lines of context

    Carefully examine all output. Sometimes one denial masks subsequent ones.

    Step 2: Analyze the Denial Message

    Let’s assume you get this denial:

    avc: denied { find } for pid=789 comm="vendor.hal" scontext=u:r:vendor_hal:s0 tcontext=u:object_r:hal_service_type:s0 tclass=service_manager permissive=0

    From this, we extract:

    • scontext domain: vendor_hal
    • tcontext type: hal_service_type
    • tclass: service_manager
    • permission: find

    Step 3: Formulate a Policy Rule

    Based on the analysis, the missing rule is to allow the vendor_hal domain to find services labeled hal_service_type via the service_manager. The rule would look like this:

    allow vendor_hal hal_service_type:service_manager find;

    Step 4: Test with sepolicy-inject

    Inject the proposed rule and re-trigger the action:

    adb shell /data/local/tmp/sepolicy-inject -s vendor_hal -t hal_service_type -c service_manager -p find -P /sys/fs/selinux/policy
    # Re-trigger the 'vendor.hal' service or app

    Check dmesg again. If the previous denial is gone, you’ve fixed that specific issue. If new denials appear, repeat the analysis for them. It’s an iterative process.

    Step 5: Persistent Policy Integration (Build System)

    Once you’ve validated your rules using sepolicy-inject, you need to make them permanent by integrating them into your Android build system’s SELinux policy. This involves modifying or adding .te (Type Enforcement) files in your device’s sepolicy directories (e.g., device/<vendor>/<device>/sepolicy or within the HAL’s specific sepolicy folder).

    For example, you might add the rule to a file like vendor_hal.te:

    # device/<vendor>/<device>/sepolicy/vendor_hal.te
    allow vendor_hal hal_service_type:service_manager find;

    You might also need to define new types or attribute associations in .fc (file contexts) or .te files if the tcontext itself is not yet defined or mislabeled.

    Advanced Tips and Tricks

    • Use permissive mode cautiously: Temporarily setting a domain to permissive (`permissive <domain_type>` in policy, or `setenforce 0` system-wide for identification purposes) allows you to log all denials for that domain without blocking them. This can reveal a cascade of issues. Remember to revert to enforcing mode immediately after diagnosis.
    • Identify the correct tclass: Pay close attention to the tclass. A denial on a dir requires different permissions than a file within that directory, or a socket, or a binder.
    • Understanding Type Transition: Sometimes, the denial isn’t about direct access but about creating a new object with an incorrect default type. This often involves create, add_name, and setattr permissions combined with `type_transition` rules.
    • Consult AOSP Policy: The Android Open Source Project’s platform_sepolicy is the best reference for understanding how common system components are labeled and how their interactions are managed.

    Conclusion

    Debugging SELinux AVC denials on Android requires patience, a systematic approach, and the right toolkit. By thoroughly analyzing denial messages, leveraging dynamic testing with sepolicy-inject, and understanding the nuances of SELinux policy syntax, you can efficiently resolve complex security blocks. This mastery not only ensures the stability and functionality of your custom Android builds but also deepens your understanding of Android’s robust security architecture.

  • Reverse Engineering Android’s Precompiled SELinux Policy: A Deep Dive Lab

    Introduction: The Unseen Guardian of Android Security

    Android’s security model is robust, with SELinux (Security-Enhanced Linux) serving as a critical layer, enforcing mandatory access control (MAC) policies across the entire system. Unlike traditional Linux distributions where SELinux policies are often compiled from human-readable Source Intermediate Language (CIL) files on-device or during package installation, Android ships with precompiled policies. These policies, optimized for performance and integrity, can be a black box for developers and security researchers aiming to understand, debug, or customize system behavior. This deep dive lab will guide you through the process of reverse engineering Android’s precompiled SELinux policy, allowing you to peek behind the curtain and gain invaluable insights into its security enforcement mechanisms.

    Understanding Android’s SELinux policy is crucial for advanced OS customizations, debugging tricky permission issues, or hardening device security. When an application misbehaves due to an ‘avc: denied’ error, or a custom system service fails to gain necessary privileges, knowing how to interpret and decompile the policy is your first step towards a resolution.

    Prerequisites for Your Journey

    Before embarking on this reverse engineering adventure, ensure you have the following:

    • Basic understanding of SELinux concepts (domains, types, classes, permissions).
    • An Android device (rooted is ideal for full policy extraction, otherwise AOSP images can be used).
    • ADB (Android Debug Bridge) installed and configured.
    • Python 3 installed.
    • A Linux-based environment (Ubuntu/Debian recommended) for tools.
    • Git for cloning necessary repositories.

    Understanding Android’s SELinux Policy Structure

    Android’s SELinux policy isn’t a single monolithic file. It’s a complex, multi-layered structure compiled from various `.te` (type enforcement) files across the AOSP source tree. These `.te` files are then translated into CIL and finally compiled into a binary policy format. On a running Android device, you’ll typically find the active policy at /sys/fs/selinux/policy. Additionally, policy components are often distributed across partitions:

    • /plat_sepolicy.cil: Platform-specific policy.
    • /vendor/etc/selinux/vendor_sepolicy.cil: Vendor-specific policy.
    • /system_ext/etc/selinux/system_ext_sepolicy.cil: System-extension policy.

    These CIL files, though still compiled forms, are a step closer to human readability than the kernel’s active binary policy. The challenge lies in converting the kernel’s binary policy into something we can analyze effectively.

    Decompiling the Binary Policy

    The core of reverse engineering is decompiling the active binary SELinux policy. While tools like sesearch and sepolicy-analyze can query a compiled policy, they don’t give us the full CIL source. For Android, the most reliable method involves using the apolicy tool, which is part of the AOSP source or can be found in various forks.

    Step 1: Extracting the Active Policy

    First, we need to pull the active policy from your Android device. This requires root access or specific permissions, as /sys/fs/selinux/policy is often protected.

    adb root adb pull /sys/fs/selinux/policy .

    If `adb root` fails, you might need to find a way to get root access on your specific device, or alternatively, use a custom AOSP build where you can access the compiled policy file directly from the build output (e.g., out/target/product/<device>/root/sepolicy).

    Step 2: Obtaining and Using apolicy

    The apolicy tool is designed to decompile Android’s specific policy format. You can typically find it within the AOSP source tree under external/selinux/prebuilts/bin/apolicy or by cloning a standalone version:

    git clone https://github.com/chenxiaolong/apolicy.git cd apolicy

    Now, use `apolicy` to decompile the extracted policy. Ensure you have Python 3 installed.

    python3 apolicy.py decompile ../policy > decompiled_policy.cil

    This command will take your binary `policy` file and output its CIL representation into `decompiled_policy.cil`. The output will be a very large CIL file, representing the entire policy in a somewhat readable format.

    Analyzing the Decompiled Policy (CIL)

    With the `decompiled_policy.cil` in hand, you can now analyze the policy rules. The CIL format is structured and allows you to understand types, attributes, roles, and rules. Common commands for analysis include `grep`, `sesearch` (from `libsepol-devel` package, often available on Linux distributions), and `audit2allow` (though `audit2allow` is primarily for generating new rules from denials, it helps understand rule structures).

    Identifying Types and Attributes

    You can search for type definitions, which define the security contexts for processes and objects.

    grep

  • Automated Android Network Diagnostics: A BPF Scripting Masterclass

    Diagnosing network issues on Android devices can often feel like peering into a black box. Traditional user-space tools provide limited visibility, struggling to capture transient, low-level network events or deeply analyze kernel-level packet processing. This is where the Berkeley Packet Filter (BPF) emerges as a game-changer. By enabling custom, programmable logic directly within the Linux kernel, BPF offers unparalleled visibility and control, transforming the landscape of Android network diagnostics.

    The Power of BPF on Android

    BPF, originally designed for efficient packet filtering, has evolved into a versatile virtual machine within the Linux kernel. Its extended version, eBPF, allows developers to run sandboxed programs responding to various kernel events, from network packets to system calls and function entries/exits. This capability is revolutionary for Android, a platform built atop the Linux kernel.

    Why BPF for Android?

    • Kernel-Level Visibility: Directly observe network stack operations, bypassing user-space limitations.
    • High Performance: BPF programs execute in-kernel, minimizing overhead compared to traditional tracing or logging.
    • Safety & Stability: Programs undergo a verifier to ensure they don’t crash the kernel or access unauthorized memory.
    • No User-Space Polling: Events are captured and processed efficiently, reducing battery drain and resource consumption.
    • Dynamic & Programmable: Adapt diagnostics on the fly without recompiling or rebooting the device.

    BPF’s Role in Modern Android

    Modern Android versions increasingly leverage BPF for various system services, including network monitoring, security policy enforcement (e.g., bpf_maps for network data and statistics in Android 10+), and performance tuning. Understanding BPF is crucial for anyone looking to perform expert-level diagnostics or contribute to the platform’s core functionalities.

    Setting Up Your BPF Development Environment

    To embark on your BPF scripting journey for Android, you’ll need a robust development environment. This typically involves building a custom Android kernel or verifying BPF support on a device, along with the necessary cross-compilation tools.

    Prerequisites

    • An Android device or emulator with a kernel supporting BPF (CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_KPROBE_EVENTS=y).
    • Android NDK (for cross-compiling user-space BPF loaders).
    • clang compiler with BPF backend (usually available with recent NDKs or system-wide LLVM).
    • bpftool and libbpf, compiled for your host system and potentially for Android.

    AOSP & Kernel Build Environment

    For most advanced BPF applications, you’ll likely need to compile your own Android kernel to ensure all necessary BPF configurations are enabled. This involves setting up an AOSP build environment. Verify your kernel’s BPF capabilities:

    adb shell zcat /proc/config.gz | grep BPF_SYSCALL
    adb shell zcat /proc/config.gz | grep KPROBE_EVENTS

    If these return CONFIG_BPF_SYSCALL=y and CONFIG_KPROBE_EVENTS=y, your kernel is ready for BPF tracing. Otherwise, you’ll need to modify your kernel’s .config and rebuild.

    Installing bpftool and libbpf

    bpftool is essential for loading, managing, and inspecting BPF programs and maps. You can build it from the Linux kernel source tree:

    git clone https://github.com/torvalds/linux.git
    cd linux/tools/bpf/bpftool
    make
    sudo make install

    For Android, you might need to statically compile bpftool or portions of libbpf and push them to the device for on-device execution, especially when working on unrooted devices with limited tools.

    Crafting Your First BPF Network Diagnostic Script

    Let’s illustrate BPF’s power by creating a script to monitor TCP retransmissions, a common indicator of network instability or congestion.

    Use Case: Monitoring TCP Retransmissions

    TCP retransmissions occur when a sender doesn’t receive an acknowledgment for sent data within a timeout. High retransmission rates can signify poor signal, congested networks, or even faulty network hardware/drivers. We’ll use a BPF kprobe to hook into the kernel’s tcp_retransmit_skb function.

    BPF Program Structure (retransmit_monitor.c)

    #include <vmlinux.h>
    #include <bpf/bpf_helpers.h>
    #include <bpf/bpf_tracing.h>
    
    char LICENSE[] SEC("license") = "GPL";
    
    // Define a BPF map to store retransmission counts per PID
    struct {
        __uint(type, BPF_MAP_TYPE_HASH);
        __uint(max_entries, 1024);
        __type(key, __u32);   // PID
        __type(value, __u64); // Retransmission count
    } retransmit_counts SEC(".maps");
    
    SEC("kprobe/tcp_retransmit_skb")
    int BPF_KPROBE(tcp_retransmit_skb, struct sock *sk, struct sk_buff *skb)
    {
        // Get the current process ID
        __u32 pid = bpf_get_current_pid_tgid() >> 32;
    
        // Increment retransmission count for this PID
        __u64 *count = bpf_map_lookup_elem(&retransmit_counts, &pid);
        if (count) {
            (*count)++;
        } else {
            __u64 init_count = 1;
            bpf_map_update_elem(&retransmit_counts, &pid, &init_count, BPF_ANY);
        }
    
        bpf_printk("TCP retransmission detected for PID: %d", pid);
        return 0;
    }

    Compiling the BPF Program

    You need a clang compiler that supports the BPF target. Using the Android NDK’s clang is often the easiest route, ensuring compatibility with your target Android kernel headers.

    /path/to/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/clang -target bpf -O2 -emit-llvm -c retransmit_monitor.c -o retransmit_monitor.ll
    /path/to/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/llc -filetype obj -march=bpf retransmit_monitor.ll -o retransmit_monitor.o

    This will produce retransmit_monitor.o, an ELF object file containing your BPF bytecode.

    Loading and Attaching the BPF Program on Android

    First, push the compiled BPF object and bpftool (if not pre-installed) to your Android device:

    adb push retransmit_monitor.o /data/local/tmp/
    adb push /path/to/bpftool /data/local/tmp/ # If bpftool isn't on device

    Now, load and attach the program. This typically requires root privileges on the device:

    adb shell
    su
    cd /data/local/tmp/
    
    # Load the BPF program
    ./bpftool prog load retransmit_monitor.o /sys/fs/bpf/retransmit_monitor
    
    # Attach the kprobe to the kernel function
    ./bpftool link create prog_id $(./bpftool prog show name retransmit_monitor -json | jq '.[0].id') 
      type kprobe attach_target tcp_retransmit_skb_fentry
    
    # Alternatively, for older bpftool or direct kprobe event
    # echo 'p:tcp_retransmit_skb /data/local/tmp/retransmit_monitor' > /sys/kernel/debug/tracing/kprobe_events
    # echo 1 > /sys/kernel/debug/tracing/events/kprobes/p_tcp_retransmit_skb/enable
    
    # Monitor BPF printk output
    cat /sys/kernel/debug/tracing/trace_pipe
    
    # To read map data (from host, requires libbpf/bpf_map_get_next_key)
    # For simplicity on device, you might need a small C program linked with libbpf to read maps.
    # Or, on a host with a compatible libbpf:
    # bpftool map dump id <map_id>

    Replace <map_id> with the actual ID of your retransmit_counts map, which you can find using bpftool map show.

    Advanced BPF Techniques for Android

    Leveraging BPF Maps for Statefulness

    BPF maps are crucial for storing state, sharing data between BPF programs, or communicating with user-space applications. Our retransmit_monitor.c example already uses a BPF_MAP_TYPE_HASH to store per-PID retransmission counts. Other map types include arrays, LPM (Longest Prefix Match) for routing lookups, and ring buffers for event streaming.

    User-Space Integration with libbpf

    For real-world Android applications, you’ll want your native (C/C++) user-space components to interact with BPF programs. The libbpf library simplifies this, providing APIs to load BPF objects, attach programs, and read/write from BPF maps. You can integrate libbpf into your Android NDK project to build sophisticated diagnostic tools that collect and present BPF data.

    Dynamic Tracing with uprobes and kprobes

    Beyond kernel functions, BPF allows tracing user-space functions (uprobes) within any application. This means you can monitor specific network-related calls within a problematic Android app, gaining insight into its internal network behavior without modifying its source code or recompiling it. This level of dynamic introspection is invaluable for deep debugging.

    Conclusion

    BPF scripting opens up an entirely new realm of possibilities for Android network diagnostics. By providing a secure, performant, and highly programmable interface to the kernel, it empowers developers and system administrators to pinpoint network bottlenecks, identify application-specific issues, and gain an unprecedented understanding of network traffic flow on Android devices. Mastering BPF is no longer an optional skill for advanced Android analysis; it is a fundamental requirement for anyone striving for expert-level system insights.

  • Boost Android Network Performance: Optimizing Latency & Throughput with eBPF

    Introduction: The Quest for Flawless Mobile Networking

    In today’s hyper-connected world, mobile device network performance is paramount. From streaming high-definition video to real-time gaming and mission-critical applications, low latency and high throughput are no longer luxuries but expectations. Android, powering the vast majority of smartphones, faces unique challenges in optimizing its network stack due to diverse hardware, varied network conditions, and the inherent complexity of a full-featured operating system. Traditional methods often involve user-space tweaking or extensive kernel recompilations, which are cumbersome and risk system instability.

    Enter eBPF (extended Berkeley Packet Filter), a revolutionary in-kernel virtual machine that allows developers to run sandboxed programs within the Linux kernel, dynamically and safely. Originally designed for network packet filtering, eBPF has evolved into a versatile tool for high-performance networking, tracing, and security. For Android, eBPF offers an unprecedented opportunity to fine-tune network behavior at a granular level, addressing bottlenecks that were previously intractable without deeply invasive kernel modifications.

    Understanding eBPF’s Role in Android Networking

    eBPF programs are event-driven, triggered when the kernel or an application passes a certain hook point. These programs can inspect, modify, and even drop packets, collect performance metrics, and interact with various kernel subsystems. The key advantages for Android include:

    • Kernel-level Performance: eBPF programs execute directly within the kernel, avoiding costly context switches between user space and kernel space, leading to significantly lower latency and higher processing rates.
    • Dynamic Programmability: Programs can be loaded, updated, and unloaded without requiring a kernel reboot or recompilation, enabling agile experimentation and deployment of optimizations.
    • Safety: The eBPF verifier ensures that programs are safe to run, preventing infinite loops or memory access violations that could crash the kernel.
    • Observability: Beyond control, eBPF provides deep insights into kernel operations, allowing for precise identification of network bottlenecks.

    eBPF Hook Points for Network Optimization

    eBPF provides several critical hook points relevant to network performance:

    • Traffic Control (tc BPF): Programs attached to the Linux traffic control subsystem can perform packet classification, filtering, and manipulation at ingress and egress points of network interfaces. This is ideal for Quality of Service (QoS), basic firewalling, and traffic shaping.
    • XDP (eXpress Data Path): XDP allows eBPF programs to run directly on the network driver’s receive path, even before the kernel’s network stack fully processes a packet. This provides the earliest possible opportunity to drop, redirect, or modify packets, offering extreme performance for DDoS mitigation or specialized routing. While promising, XDP support on Android devices is highly dependent on specific network hardware drivers.
    • Socket Filters: Attach eBPF programs directly to sockets to filter or modify data before it reaches user-space applications.
    • Tracing (kprobes, uprobes, tracepoints): Monitor kernel and user-space functions related to networking (e.g., TCP stack functions, socket calls) to gather detailed performance metrics and debug issues without overhead.

    Setting Up Your Android eBPF Development Environment

    To experiment with eBPF on Android, you’ll typically need a rooted device or, ideally, a custom Android Open Source Project (AOSP) build with appropriate kernel configurations. For full control and the latest features, building AOSP is recommended.

    1. Kernel Configuration

    Ensure your Android kernel is configured with eBPF support. Key kernel configurations (usually found in kernel/configs/android-*-*-defconfig within your AOSP tree) include:

    CONFIG_BPF=yCONFIG_BPF_SYSCALL=yCONFIG_BPF_JIT=yCONFIG_BPF_EVENTS=yCONFIG_NET_CLS_BPF=yCONFIG_NET_ACT_BPF=y# Optional, for XDP, if supported by hardwareCONFIG_XDP_SOCKETS=yCONFIG_XDP_BPF_PROGRAM=y

    After modifying the kernel configuration, rebuild and flash your AOSP image to the target device.

    2. Build Tools and Environment

    You’ll need a clang toolchain capable of targeting BPF bytecode. This is typically included in AOSP builds or can be set up manually:

    # Assuming AOSP build environment has been sourcedexport PATH=$PATH:<AOSP_ROOT>/prebuilts/clang/host/linux-x86/clang-rXXX/bin/export CLANG_TRIPLE=aarch64-linux-gnu-# For local clang installation, ensure it supports BPF target# Check if 'bpf' is in the output of: clang --print-target-triple

    You’ll also need bpftool and libbpf, which are usually built as part of the Android platform if the kernel has BPF support. These tools are crucial for loading, attaching, and managing eBPF programs and maps.

    # On your Android device via adb shell, or during AOSP buildbpftool prog showbpftool map show

    Practical Example: Basic Packet Filtering with tc BPF

    Let’s illustrate a simple eBPF program to drop all incoming ICMP (ping) packets on a specific network interface. This demonstrates how to intercept and control network traffic at the kernel level.

    1. eBPF Program (drop_icmp.c)

    This C program, compiled to BPF bytecode, checks if an incoming packet is an IP packet and if its protocol is ICMP. If so, it instructs the kernel to drop the packet.

    #include <linux/bpf.h>#include <linux/pkt_cls.h>#include <linux/if_ether.h>#include <linux/ip.h>#include <linux/icmp.h>#include <bpf/bpf_helpers.h>SEC(

  • Android SELinux Policy From Scratch: A Step-by-Step Guide for Custom Services

    Introduction to SELinux on Android

    SELinux (Security-Enhanced Linux) is a mandatory access control (MAC) system that enhances the security of Android devices by confining programs and processes to the minimum set of privileges they need. Unlike traditional discretionary access control (DAC) where permissions are based on user and group IDs, SELinux policies define explicit rules for what processes can access which resources, regardless of their user context. When developing custom services or daemons for Android, understanding and writing correct SELinux policy is paramount to ensure both security and functionality.

    This guide will walk you through the process of creating a new SELinux policy for a hypothetical custom service from the ground up, covering the essential concepts, debugging techniques, and best practices.

    Prerequisites

    • An Android Open Source Project (AOSP) build environment.
    • Basic familiarity with Linux command-line operations.
    • A rooted Android device or an AOSP emulator to test policies.
    • Understanding of the Android build system (Android.bp, init.rc).

    Understanding SELinux Basics for Android

    SELinux operates on a principle of ‘everything is denied unless explicitly allowed’. Key concepts include:

    • Subjects (Processes): These are labeled with a domain or type.
    • Objects (Files, Sockets, IPC): These are labeled with a type.
    • Rules: Define interactions between subjects and objects (e.g., allow source_type target_type:class permissions;).
    • Contexts: A string label (e.g., u:r:myservice_daemon:s0) applied to processes, files, and other objects. For Android, the user (u) and role (r) are typically fixed, while the type (myservice_daemon) is the primary component we modify.

    When an unallowed action occurs, SELinux generates an Audit Violation Message (AVC denial), which is crucial for debugging.

    Scenario: A Custom ‘myservice’ Daemon

    Let’s imagine we’re adding a simple native C daemon named myservice to Android. This service will run in the background, perform some operations, and potentially log to a file. Our goal is to create a secure SELinux policy that allows myservice to function correctly without granting it excessive privileges.

    Step 1: Create the Custom Service Executable

    First, create a simple C file, myservice.c:

    #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(int argc, char *argv[]) { printf(