Binder安全

继续修改demo,加一些校验。

AndroidManifest中定义权限:

remoteService挂载权限:

RemoteService 里的 add(int a, int b) ,在业务代码之前插入权限检查:

运行:

缺少权限声明,看来同一个app的不同进程之间调用也不能缺少权限声明,加上:

再测试:

嗯 这次没报错。

再修改下demo,对调用方包名做白名单校验。

在RemoteService 的 IMyAidlInterface.Stub 里,这样校验白名单写:

然后在每个 AIDL 方法的第一行调用它:

测试:

可以看到,能获取到调用方包名。ok.

如果不想每个aidl方法都加校验,也可以在Stub 的 onTransact方法中统一拦截,这里:

ok. 完整代码。RemoteService:

java 复制代码
package com.example.binderdemo;

import android.app.Service;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Process;
import android.os.RemoteException;
import android.util.Log;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class RemoteService extends Service {
    private static final String TAG = "RemoteService";

//    private final List<ICallback> callbacks = new ArrayList<>();
    private final android.os.RemoteCallbackList<ICallback> callbacks =
            new android.os.RemoteCallbackList<>();

    private final android.os.Handler handler =
            new android.os.Handler(android.os.Looper.getMainLooper());

    private final Runnable tick = new Runnable() {
        @Override
        public void run() {
            int n = callbacks.beginBroadcast();
            Log.d(TAG, "tick, callbacks.size=" + n);
            Log.d(TAG, "tick, callbacks.size=" + n + ", 何以解忧,唯有退休?");
            try {
                for (int i = 0; i < n; i++) {
                    ICallback cb = callbacks.getBroadcastItem(i);
                    try {
                        cb.onResult(9527);
                    } catch (RemoteException e) {
                        Log.e(TAG, "tick onResult failed: " + e);
                    }
                }
            } finally {
                callbacks.finishBroadcast();
            }
            handler.postDelayed(this, 10000);
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate, start tick");
        handler.postDelayed(tick, 10000);
    }

    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroy, stop tick");
        handler.removeCallbacks(tick);
        super.onDestroy();
    }

    private void broadcastResult(int result) {
        int n = callbacks.beginBroadcast();
        try {
            for (int i = 0; i < n; i++) {
                ICallback cb = callbacks.getBroadcastItem(i);
                try {
                    cb.onResult(result);
                } catch (RemoteException e) {
                    Log.e(TAG, "onResult failed: " + e);
                }
            }
        } finally {
            callbacks.finishBroadcast();
        }
    }

    private static final Set<String> ALLOWED_PACKAGES =
            new HashSet<>(Arrays.asList("com.example.binderdemo"));

    private final IMyAidlInterface.Stub binder = new IMyAidlInterface.Stub() {
        @Override
        public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            // 也可以在这里统一拦截。
//            if (!isCallerAllowed()) {
//                return false;  // 或不返回结果,让调用方收到异常
//            }
            return super.onTransact(code, data, reply, flags);
        }

        private boolean isCallerAllowed() {
            int callingUid = Binder.getCallingUid();
            // uid 0 是 root,视场景决定是否放行
            if (callingUid == 0) return false;

            String[] packages = getPackageManager().getPackagesForUid(callingUid);
            if (packages == null) return false;

            Log.d(TAG, "packages: " + Arrays.toString(packages));

            for (String pkg : packages) {
                if (ALLOWED_PACKAGES.contains(pkg)) {
                    return true;
                }
            }
            return false;
        }

        @Override
        public int add(int a, int b) throws RemoteException {
            if (!isCallerAllowed()) {
                throw new SecurityException(
                        "Caller uid=" + Binder.getCallingUid() + " is not whitelisted");
            }

            // 关键:在执行逻辑之前,检查调用方是否持有权限
            if (checkCallingPermission("com.example.binderdemo.ACCESS_REMOTE_SERVICE")
                    != PackageManager.PERMISSION_GRANTED) {
                throw new SecurityException(
                        "Caller pid=" + Binder.getCallingPid()
                                + " uid=" + Binder.getCallingUid()
                                + " lacks ACCESS_REMOTE_SERVICE. 总舵主:熙官,别来无恙。");
            }

            Log.d(TAG, "add(" + a + "," + b + ") START thread=" + Thread.currentThread().getName()
                    + ", pid=" + Process.myPid());

            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            Log.d(TAG, "add(" + a + "," + b + ") END   thread=" + Thread.currentThread().getName());

            int result = a + b;
            // 不在 Binder 线程里广播,post 到主线程统一做
            handler.post(() -> broadcastResult(result));

            return result;
        }

        @Override
        public String getMessage() {
            if (!isCallerAllowed()) {
                throw new SecurityException(
                        "Caller uid=" + Binder.getCallingUid() + " is not whitelisted");
            }

            Log.d(TAG, "getMessage thread="
                    + Thread.currentThread().getName()
                    + ", pid=" + Process.myPid());
            return "Hello from pid=" + Process.myPid() + " , 为人不识陈近南,纵称英雄也枉然。";
        }

        @Override
        public void registerCallback(ICallback cb) throws RemoteException {
            if (!isCallerAllowed()) {
                throw new SecurityException(
                        "Caller uid=" + Binder.getCallingUid() + " is not whitelisted");
            }

            if (cb == null) return;
            Log.d(TAG, "registerCallback cb=" + cb
                    + ", class=" + cb.asBinder().getClass().getName()
                    + ", thread=" + Thread.currentThread().getName());
            callbacks.register(cb);
        }

        @Override
        public void unregisterCallback(ICallback cb) throws RemoteException {
            if (!isCallerAllowed()) {
                throw new SecurityException(
                        "Caller uid=" + Binder.getCallingUid() + " is not whitelisted");
            }

            if (cb != null) {
                callbacks.unregister(cb);
            }
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "onBind thread="
                + Thread.currentThread().getName()
                + ", pid=" + Process.myPid());
        return binder;
    }
}

AndroidManifest:

XML 复制代码
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <!-- 定义自定义权限:signature 级别最严格,只允许同签名应用持有 -->
    <permission
        android:name="com.example.binderdemo.ACCESS_REMOTE_SERVICE"
        android:protectionLevel="signature" />

    <uses-permission android:name="com.example.binderdemo.ACCESS_REMOTE_SERVICE" />

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Binderdemo">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:label="@string/app_name"
            android:theme="@style/Theme.Binderdemo">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service android:name=".RemoteService"
            android:process=":remote"
            android:exported="true"
            android:permission="com.example.binderdemo.ACCESS_REMOTE_SERVICE"/>

        <service android:name=".RemoteService2"
            android:process=":remote"
            android:exported="false"/>
    </application>

</manifest>

MainActivity:

java 复制代码
package com.example.binderdemo;

import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.util.Log;
import android.widget.TextView;

import androidx.activity.ComponentActivity;
import androidx.annotation.Nullable;

import static com.example.binderdemo.IMyAidlInterface.DESCRIPTOR;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MainActivity extends ComponentActivity {
    private static final String TAG = "ClientMainActivity";

    private IMyAidlInterface remote;

    private final ICallback callback = new ICallback.Stub() {
        @Override
        public void onResult(int result) {
            Log.d(TAG, "onResult result=" + result
                    + ", thread=" + Thread.currentThread().getName() // binder线程
                    + ", pid=" + Process.myPid());
            runOnUiThread(() -> tv.setText("callback result=" + result));
        }
    };

//private IMiniInterface remote;
    private TextView tv;
    private boolean bound;

    private final ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d(TAG, "onServiceConnected thread="
                    + Thread.currentThread().getName());
//            // 这个service类型是android.os.BinderProxy,远端代理,用于跨进程通信。
//            // 这里asInterface返回一个客户端Proxy对象,里面的成员变量mRemote就是这个service对象。
//            remote = IMyAidlInterface.Stub.asInterface(service);

            // 手写版 asInterface:跨进程直接返回 Proxy
//            remote = new MiniProxy(service);

//            android.os.IInterface iin = service.queryLocalInterface(DESCRIPTOR); // 跨进程通信时,搜索为null
//            Log.d(TAG, "iin=" + iin); // iin=null

            remote = IMyAidlInterface.Stub.asInterface(service);
            try {
                // 先注册callback
                remote.registerCallback(callback);
                Log.d(TAG, "registered callback, callback.asBinder()="
                        + callback.asBinder().getClass().getName());
                // 然后调用add方法
                int result = remote.add(1, 2);
//                String msg = remote.getMessage();

//                Log.d(TAG, "add result=" + result + ", msg=" + msg);
//                tv.setText("result=" + result + "\n" + msg);
                tv.setText("add result=" + result);
                Log.d(TAG, "当前客户端进程:"+ Process.myPid());
                Log.d(TAG, "remote: " + remote);
                Log.d(TAG, "service: " + service);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            Log.d(TAG, "client pid=" + Process.myPid());
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d(TAG, "onServiceDisconnected");
            remote = null;
        }
    };

    private void concurrentCalls() {
        if (remote == null) {
            Log.d(TAG, "remote is null, not connected");
            return;
        }

        final int N = 20;
        ExecutorService pool = Executors.newFixedThreadPool(N);
        final CountDownLatch ready = new CountDownLatch(N);
        final CountDownLatch start = new CountDownLatch(1);

        final long begin = System.currentTimeMillis();

        for (int i = 0; i < N; i++) {
            final int idx = i;
            pool.execute(() -> {
                ready.countDown();
                try {
                    start.await();   // 等所有线程就绪,一起发
                    Log.d(TAG, "call add #" + idx + " thread="
                            + Thread.currentThread().getName());
                    int r = remote.add(idx, idx);
                    Log.d(TAG, "call add #" + idx + " result=" + r
                            + ", elapsed=" + (System.currentTimeMillis() - begin));
                } catch (Exception e) {
                    Log.d(TAG, "error: " + e);
                }
            });
        }

        try {
            ready.await();
            start.countDown();   // 发令枪
        } catch (InterruptedException e) {
            Log.d(TAG, "err: " + e);
        }

        pool.shutdown();
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        tv = new TextView(this);
        // 加一个按钮,点击杀掉自己进程
        android.widget.Button killBtn = new android.widget.Button(this);
        killBtn.setText("杀掉客户端进程");
        killBtn.setOnClickListener(v -> {
            Log.d(TAG, "kill self, pid=" + Process.myPid());
            android.os.Process.killProcess(android.os.Process.myPid());
        });

        android.widget.LinearLayout root = new android.widget.LinearLayout(this);
        root.setOrientation(android.widget.LinearLayout.VERTICAL);
        root.addView(tv);
        root.addView(killBtn);
        setContentView(root);

        android.widget.Button concurrentBtn = new android.widget.Button(this);
        concurrentBtn.setText("并发调用 add x20");
        concurrentBtn.setOnClickListener(v -> concurrentCalls());
        root.addView(concurrentBtn);

        Intent intent = new Intent(this, RemoteService.class);
//        Intent intent = new Intent(this, RemoteService2.class);
        startService(intent);
        bound = bindService(intent, conn, BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        if (bound) {
            try {
                if (remote != null) {
                    remote.unregisterCallback(callback);
                }
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            unbindService(conn);
            bound = false;
        }
        super.onDestroy();
    }
}

ok.