RK3576-Android15_Usb白名单功能实现篇二
实现USB 白名单功能
文章目录
- 前言-需求
- 一、 参考资料
- 二、 修改文件-修改方案
- 修改文件
- 实现方案
- 配置属性,表示三种模式
- UsbHostManager 类 openDevice 方法中添加逻辑
- StorageManagerService 存储服务中-拦截Usb 进行卸载
- DiskInfo 源码分析
- 小结
- 三、源码分析
- 为什么 UsbHostManager 中 usbDeviceAdded 拦截不起作用
- StorageManagerService 源码分析
- IVoldListener 知识点
- IVoldListener.aidl
- onVolumeCreatedLocked 方法
- IVold 回调 IVoldListener接口
- ServiceManager.getService("vold") 中vold 分析 - VolumeBase.cpp
- 为什么不再守护进程 VolumeBase.cpp 中实现拦截逻辑
- 总结
前言-需求
需求:USB 类型设备接入白名单
1) 默认: USB类型设备都可以接入
2) 全部拦截: USB类型设备全部不可用接入
3)根据vid/pid 进行拦截,指定vid pid 类型设备才可以接入使用
备注:USB类型很多的,目前主要分为:U盘、外接UVC 相机、Camera2相机打开外接的USBCamera
一、 参考资料
RK-USB白名单功能实现
Android 12系统源码_存储(二)StorageManagerService服务
Android存储系统源码走读(一):StorageManagerService
Android存储系统源码走读(二):vold
二、 修改文件-修改方案
修改文件
device\rockchip\rk3576\rk3576_u\rk3576_u.mk
frameworks\base\services\usb\java\com\android\server\usb\UsbHostManager.java
frameworks\base\core\java\android\os\storage\DiskInfo.java
\frameworks\base\services\core\java\com\android\server\StorageManagerService.java
实现方案
如下只是提供部分思路,实际代码和逻辑还需自己分析
配置属性,表示三种模式
device\rockchip\rk3576\rk3576_u\rk3576_u.mk
添加两个属性:persist.fise.custom.usbmode 、 persist.fise.custom.usbdata
分别用来表示当前USB模式和vid /pid
拦截下USB模式下存储的vid/pid
数据
UsbHostManager 类 openDevice 方法中添加逻辑
在 openDevice 方法中,进行拦截,如下伪代码,可参考: 核心思想就是你在打开USB设备的时候进行拦截过滤
/*** Opens the specified USB device*/public ParcelFileDescriptor openDevice(String deviceAddress,UsbUserPermissionManager permissions, String packageName, int pid, int uid) {synchronized (mLock) {if (isDenyListed(deviceAddress)) {throw new SecurityException("USB device is on a restricted bus");}UsbDevice device = mDevices.get(deviceAddress);if (device == null) {// if it is not in mDevices, it either does not exist or is denylistedthrow new IllegalArgumentException("device " + deviceAddress + " does not exist or is restricted");}// modify by fangchen start Log.d(TAG,"=========openDevice=======:openDevice packageName:"+packageName+" pid:"+pid+" uid:"+uid);String usbMode = SystemProperties.get(PERSIST_FISE_CUSTOM_USBMODE);int dVid = device.getVendorId();int dPid = device.getProductId();Log.d(TAG,"=========openDevice:=======dVid:"+dVid+" dPid:"+dPid);if("2".equals(usbMode)){Log.d(TAG,"getDeviceList usbMode ==2 custom vidpidWhiteList filter ");String usbData = SystemProperties.get(PERSIST_FISE_CUSTOM_USBDATA);if (!TextUtils.isEmpty(usbData)) {Log.d(TAG, "getDeviceList======================usbData:==:"+usbData);Log.d(TAG, "getDeviceList==== usbData:"+usbData);List<String> newList = Arrays.asList(usbData.split(","));vidpid_whiteList=newList;}if(!this.vidpid_whiteList.contains( dVid+""+ dPid)){throw new IllegalArgumentException("device " + deviceAddress + " forbiden all not in whiteList");}} if("1".equals(usbMode)){Log.d(TAG," forbiden all ...");throw new IllegalArgumentException("device " + deviceAddress + " does not exist permission");}Log.d(TAG," zhunbei dakai nativeOpenDevice");// modify by fangchen end permissions.checkPermission(device, packageName, pid, uid);return nativeOpenDevice(deviceAddress);}}
StorageManagerService 存储服务中-拦截Usb 进行卸载
当U盘类型设备插入后 ,内核自动识别到型号, 通过过滤操作,让不符合类型的设备不让挂载掉。 因为挂载逻辑是底层 U盘本身属于热插拔机制,跟内核关联的。 插入后信号uevent 信号到中间层,中间层Framework 调用指令自动挂载了,所以最优解决方案是内核周公来进行拦截信号,但是就不用想了吧… 要user 空间和内核空间交换数据、动态处理存储,算了吧 行不通的。
部分伪代码,参考如下:
................ else if (vol.type == VolumeInfo.TYPE_PUBLIC) {// TODO: only look at first public partitionString fSuid=vol.getFsUuid();int type=vol.getType();DiskInfo diskInfo=vol.getDisk();if(diskInfo!=null){Log.d(TAG," diskInfo!=null ");boolean isUsb=diskInfo.isUsb();boolean isTF=diskInfo.isSd();Log.d(TAG," fSuid:"+fSuid+" type:"+type+" isUsb:"+isUsb+" isTF:"+isTF);String diskSysPath=diskInfo.getSysPath();Log.d(TAG," ===============================diskSysPath="+diskSysPath);String fileFirstStr = diskSysPath.replaceFirst(":.*", "");Log.d(TAG,"================fileFirstStr======:"+fileFirstStr);String fileSecondStr = fileFirstStr.replaceFirst("/[^/]*$", "");Log.d(TAG,"================fileSecondStr======:"+fileSecondStr);String idVendor = readSysFsFile(fileSecondStr+"/idVendor");String idProduct = readSysFsFile(fileSecondStr+"/idProduct");int idVendorInt = Integer.parseInt(idVendor, 16);int idProductInt = Integer.parseInt(idProduct, 16);if (idVendor != null) {Log.d(TAG, "Vendor ID: " + idVendor);} else {Log.e(TAG, "Failed to read Vendor ID");}if (idProduct != null) {Log.d(TAG, "idProduct ID: " + idProduct);} else {Log.e(TAG, "Failed to read idProduct ID");}Log.d(TAG,"=============getUsbVidPid shiliu:="+idVendor+"_"+idProduct);Log.d(TAG,"=============getUsbVidPid shi:="+idVendorInt+"_"+idProductInt);String usbMode = SystemProperties.get(PERSIST_FISE_CUSTOM_USBMODE);Log.d(TAG,"==============================================================");if("1".equals(usbMode)){Log.d(TAG,"=======================usbMode 1 =======forbden all================================");return;}if("2".equals(usbMode)){Log.d(TAG,"usbMode ==2 custom vidpidWhiteList filter ");String usbData = SystemProperties.get(PERSIST_FISE_CUSTOM_USBDATA);Log.d(TAG,"====deviceList usbData:"+usbData);if (!TextUtils.isEmpty(usbData)) {Log.d(TAG, "update==== usbData:"+usbData);List<String> newList = Arrays.asList(usbData.split(","));vidpid_whiteList=newList;Log.d(TAG,"==========vidpid_whiteList:"+vidpid_whiteList);Log.d(TAG,"==========vidpid_whiteList:"+vidpid_whiteList);String realVidPidStr=idVendorInt +""+idProduct;Log.d(TAG,"=====================realVidPidStr 1 : idVendorInt:="+idVendorInt+" idProduct:"+idProduct);Log.d(TAG,"=====================realVidPidStr 2 :="+realVidPidStr);if(!vidpid_whiteList.contains(realVidPidStr)){Log.d(TAG,"=======================usbMod 2 not in whiteList return ");return;}else{Log.d(TAG,"=======================in vidpid_whiteList can mount ");}} else{Log.d(TAG,"====usbMode ==2 usbData is empty, no usb can mount=");return;}}}else{Log.d(TAG," diskInfo= =null ");}if (Objects.equals(StorageManager.UUID_PRIMARY_PHYSICAL, mPrimaryStorageUuid)&& vol.disk.isDefaultPrimary()) {Slog.v(TAG, "Found primary storage at " + vol);vol.mountFlags |= VolumeInfo.MOUNT_FLAG_PRIMARY;vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE_FOR_WRITE;}// Adoptable public disks are visible to apps, since they meet// public API requirement of being in a stable location.if (vol.disk.isAdoptable()) {vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE_FOR_WRITE;}................
DiskInfo 源码分析
StorageService 服务里面 关联的是USB 类型的U盘,它和UsbManager 先关的USB 设备没有半毛钱关系,UsbManaer侧重于 状态管理和连接 操作。那么 UsbManager 可以获取到设备的设备信息 vid/pid 等,也就是说StorageService 本身是关联不到 vid pid 这些属性的 。
所以我们在StrorageManagerService 中会遇到致命问题,得不到vid/pid ,还好 看下如下日志和操作:在虚拟文件系统节点路径下 我们可以找到vid / pid ,那么 根据路径读取出来不就行了嘛。
所以 我们在DiskInfo 里面对相关属性 sys 设置一个get 操作就可以了嘛。 伪代码如下:
public String getSysPath() {return sysPath;}public void setSysPath(String sysPath) {this.sysPath = sysPath;}
小结
- 上面的修改方案足以实现USB类型的拦截功能了,比如打印机、比如串口通讯。
- 为什么没有实现原生Camera2 拦截UVC类型的USB设备终端? 那是 如 StorageManagerService 一样, Camera2 本身用的CameraService 和 UsbHotManager 本身是两条线,打开相机操作本身是在jni 实现,native 操作 打开的, 无法在Framework层获取vid/pid 来实现拦截操作
- 对于UsbHostManager 和 其它涉及到Usb 关联服务的区别和联系,在实际操作中才会更深刻理解的。
三、源码分析
为什么 UsbHostManager 中 usbDeviceAdded 拦截不起作用
在 RK-USB白名单功能实现 这篇文章中,我们本身介绍了解决方案,就是在usbDeviceAdded 中实现拦截的,其实这个方案本身是错误的,不行的,下面具体分析。
首先查看一下在哪里调用的:
总的来说,这里C 层的register 、 add usb 相关的逻辑,只是调用了一次Java层的方法,实际操作都是JNI操作,那么你在Framework层的Java 代码中去拦截是没有意义的,中间层只是一个状态而已。
StorageManagerService 源码分析
参考资料中的讲解的蛮详细的,细品还是能获取很多知识点:
Android 12系统源码_存储(二)StorageManagerService服务
Android存储系统源码走读(一):StorageManagerService
Android存储系统源码走读(二):vold
StorageManagerService 本身知识点蛮多在,这里重点关注挂载相关的内容。
先看类注释
/*** Service responsible for various storage media. Connects to {@code vold} to* watch for and manage dynamically added storage, such as SD cards and USB mass* storage. Also decides how storage should be presented to users on the device.*/
class StorageManagerService extends IStorageManager.Stubimplements Watchdog.Monitor, ScreenObserver
- 负责各种存储介质的服务。连接到Vold
- 监控并管理动态添加的存储设备,如SD卡和USB大容量存储设备存储。还决定如何在设备上向用户展示存储情况。
IVoldListener 知识点
源码路径:/system/vold/binder/android/os/IVoldListener.aidl
那么它一定是通过binder 通信机制
看下源码:
IVoldListener.aidl
package android.os;/** {@hide} */
oneway interface IVoldListener {void onDiskCreated(@utf8InCpp String diskId, int flags);void onDiskScanned(@utf8InCpp String diskId);void onDiskMetadataChanged(@utf8InCpp String diskId,long sizeBytes, @utf8InCpp String label, @utf8InCpp String sysPath);void onDiskDestroyed(@utf8InCpp String diskId);void onVolumeCreated(@utf8InCpp String volId,int type, @utf8InCpp String diskId, @utf8InCpp String partGuid, int userId);void onVolumeStateChanged(@utf8InCpp String volId, int state, int userId);void onVolumeMetadataChanged(@utf8InCpp String volId,@utf8InCpp String fsType, @utf8InCpp String fsUuid, @utf8InCpp String fsLabel);void onVolumePathChanged(@utf8InCpp String volId,@utf8InCpp String path);void onVolumeInternalPathChanged(@utf8InCpp String volId,@utf8InCpp String internalPath);void onVolumeDestroyed(@utf8InCpp String volId);
}
这些咋一看不都是 相关Volume相关操作嘛,不同的状态。
onVolumeCreatedLocked 方法
回顾一下 ,我们实现USB 的U盘是在哪里操作的? 如下 再次贴一下伪代码:
@GuardedBy("mLock")private void onVolumeCreatedLocked(VolumeInfo vol) {final ActivityManagerInternal amInternal =LocalServices.getService(ActivityManagerInternal.class);if (vol.mountUserId >= 0 && !amInternal.isUserRunning(vol.mountUserId, 0)) {Slog.d(TAG, "Ignoring volume " + vol.getId() + " because user "+ Integer.toString(vol.mountUserId) + " is no longer running.");return;}if (vol.type == VolumeInfo.TYPE_EMULATED) {........................} else if (vol.type == VolumeInfo.TYPE_PUBLIC) {// TODO: only look at first public partitionString fSuid=vol.getFsUuid();int type=vol.getType();DiskInfo diskInfo=vol.getDisk();if(diskInfo!=null){Log.d(TAG," diskInfo!=null ");boolean isUsb=diskInfo.isUsb();boolean isTF=diskInfo.isSd();Log.d(TAG," fSuid:"+fSuid+" type:"+type+" isUsb:"+isUsb+" isTF:"+isTF);String diskSysPath=diskInfo.getSysPath();Log.d(TAG," ===============================diskSysPath="+diskSysPath);String fileFirstStr = diskSysPath.replaceFirst(":.*", "");Log.d(TAG,"================fileFirstStr======:"+fileFirstStr);String fileSecondStr = fileFirstStr.replaceFirst("/[^/]*$", "");Log.d(TAG,"================fileSecondStr======:"+fileSecondStr);String idVendor = readSysFsFile(fileSecondStr+"/idVendor");String idProduct = readSysFsFile(fileSecondStr+"/idProduct");int idVendorInt = Integer.parseInt(idVendor, 16);int idProductInt = Integer.parseInt(idProduct, 16);if (idVendor != null) {Log.d(TAG, "Vendor ID: " + idVendor);} else {Log.e(TAG, "Failed to read Vendor ID");}if (idProduct != null) {Log.d(TAG, "idProduct ID: " + idProduct);} else {Log.e(TAG, "Failed to read idProduct ID");}Log.d(TAG,"=============getUsbVidPid shiliu:="+idVendor+"_"+idProduct);Log.d(TAG,"=============getUsbVidPid shi:="+idVendorInt+"_"+idProductInt);String usbMode = SystemProperties.get(PERSIST_FISE_CUSTOM_USBMODE);Log.d(TAG,"==============================================================");if("1".equals(usbMode)){Log.d(TAG,"=======================usbMode 1 =======forbden all================================");return;}if("2".equals(usbMode)){Log.d(TAG,"usbMode ==2 custom vidpidWhiteList filter ");String usbData = SystemProperties.get(PERSIST_FISE_CUSTOM_USBDATA);Log.d(TAG,"====deviceList usbData:"+usbData);if (!TextUtils.isEmpty(usbData)) {Log.d(TAG, "update==== usbData:"+usbData);List<String> newList = Arrays.asList(usbData.split(","));vidpid_whiteList=newList;Log.d(TAG,"==========vidpid_whiteList:"+vidpid_whiteList);Log.d(TAG,"==========vidpid_whiteList:"+vidpid_whiteList);String realVidPidStr=idVendorInt +""+idProduct;Log.d(TAG,"=====================realVidPidStr 1 : idVendorInt:="+idVendorInt+" idProduct:"+idProduct);Log.d(TAG,"=====================realVidPidStr 2 :="+realVidPidStr);if(!vidpid_whiteList.contains(realVidPidStr)){Log.d(TAG,"=======================usbMod 2 not in whiteList return ");return;}else{Log.d(TAG,"=======================in vidpid_whiteList can mount ");}} else{Log.d(TAG,"====usbMode ==2 usbData is empty, no usb can mount=");return;}}}else{Log.d(TAG," diskInfo= =null ");}if (Objects.equals(StorageManager.UUID_PRIMARY_PHYSICAL, mPrimaryStorageUuid)&& vol.disk.isDefaultPrimary()) {Slog.v(TAG, "Found primary storage at " + vol);vol.mountFlags |= VolumeInfo.MOUNT_FLAG_PRIMARY;vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE_FOR_WRITE;}// Adoptable public disks are visible to apps, since they meet// public API requirement of being in a stable location.if (vol.disk.isAdoptable()) {vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE_FOR_WRITE;}//--------------------rk-code----------------------if ("false".equals(SystemProperties.get("cts_gts.status", "false")) &&"true".equals(SystemProperties.get("ro.vendor.udisk.visible"))) {Log.d(TAG,"-----for all public volume is visible-----");vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE_FOR_WRITE;}//-------------------------------------------------vol.mountUserId = mCurrentUserId;mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();} else if (vol.type == VolumeInfo.TYPE_PRIVATE) {mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();} else if (vol.type == VolumeInfo.TYPE_STUB) {............................mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();} else {Slog.d(TAG, "Skipping automatic mounting of " + vol);}}
从伪代码上看,我们的拦截操作是在 onVolumeCreatedLocked 方法中调用的,那么 onVolumeCreatedLocked 又是被谁调用的呢?
看一下如下代码:
@Overridepublic void onVolumeCreated(String volId, int type, String diskId, String partGuid,int userId) {Trace.instant(Trace.TRACE_TAG_SYSTEM_SERVER,"SMS.onVolumeCreated: " + volId + ", " + userId);synchronized (mLock) {final DiskInfo disk = mDisks.get(diskId);final VolumeInfo vol = new VolumeInfo(volId, type, disk, partGuid);vol.mountUserId = userId;mVolumes.put(volId, vol);onVolumeCreatedLocked(vol);}}
这不就找到了 onVolumeCreated 这个方法了嘛? 就是 IVoldListener 中的回调方法嘛
IVold 回调 IVoldListener接口
上面知识点,已经知道了 IVoldListener 是一个binder 接口,我们看看实际的定义看看,为什么是怎么回调aidl 接口的:
mVold.setListener(mListener); 源码如下,
private void connectVold() {IBinder binder = ServiceManager.getService("vold");if (binder != null) {try {binder.linkToDeath(new DeathRecipient() {@Overridepublic void binderDied() {Slog.w(TAG, "vold died; reconnecting");mVold = null;connectVold();}}, 0);} catch (RemoteException e) {binder = null;}}if (binder != null) {mVold = IVold.Stub.asInterface(binder);try {mVold.setListener(mListener);} catch (RemoteException e) {mVold = null;Slog.w(TAG, "vold listener rejected; trying again", e);}} else {Slog.w(TAG, "vold not found; trying again");}...........................}
ServiceManager.getService(“vold”) 中vold 分析 - VolumeBase.cpp
这里细节不再看分析过程了,这个基本知识点。 这里要分析的就是vold 服务,是怎么回调aidl 接口的
查找源码:
我们看看几处核心的相关回调:
- onVolumeStateChanged 回调
void VolumeBase::setState(State state) {mState = state;auto listener = getListener();if (listener) {listener->onVolumeStateChanged(getId(), static_cast<int32_t>(mState),static_cast<int32_t>(mMountUserId));}
}
- onVolumePathChanged 回调
status_t VolumeBase::setPath(const std::string& path) {if (mState != State::kChecking) {LOG(WARNING) << getId() << " path change requires state checking";return -EBUSY;}mPath = path;auto listener = getListener();if (listener) listener->onVolumePathChanged(getId(), mPath);return OK;
}
- onVolumeInternalPathChanged 回调
status_t VolumeBase::setInternalPath(const std::string& internalPath) {if (mState != State::kChecking) {LOG(WARNING) << getId() << " internal path change requires state checking";return -EBUSY;}mInternalPath = internalPath;auto listener = getListener();if (listener) {listener->onVolumeInternalPathChanged(getId(), mInternalPath);}return OK;
}
- onVolumeCreated 回调
status_t VolumeBase::create() {CHECK(!mCreated);mCreated = true;status_t res = doCreate();auto listener = getListener();if (listener) {listener->onVolumeCreated(getId(), static_cast<int32_t>(mType), mDiskId, mPartGuid,mMountUserId);}setState(State::kUnmounted);return res;
}
- onVolumeDestroyed 回调
status_t VolumeBase::destroy() {CHECK(mCreated);if (mState == State::kMounted) {unmount();setState(State::kBadRemoval);} else {setState(State::kRemoved);}auto listener = getListener();if (listener) {listener->onVolumeDestroyed(getId());}status_t res = doDestroy();mCreated = false;return res;
}
为什么不再守护进程 VolumeBase.cpp 中实现拦截逻辑
这个问题其实蛮好的,毕竟底层拦截检测机制不就从源头实现了嘛? 因为这里只是 jni层, 内核拦截不显示。 这里也可以拦截,数据存储、获取、应用通讯都是难点。 假若 有相关技能,其实这里拦截也是可以的。
为什么讲到这个知识点,因为往下分析会分析到 NetLink 、udev、mdev 热插拔机制,那里才是源头解决问题的最优解! 但是技术难度太强,偏底层。
总结
- StorageMnagerService 服务了解
- Volume 通信机制、热插拔机制了解