最近因业务需要,在安卓10源码中添加了一个文件读写服务,最后发现莫名出现sdcard读写权限问题,我默认为系统服务肯定是能读写sdcard,结果发现其实不是,下面记录一下问题过程和解决方法
// frameworks/base/core/java/android/app/util
package android.app.util;
interface IFileManager
{
String readFile(String path);
void writeFile(String path,String data);
String shellExec(String cmd);
}
需要在文件"frameworks\base\Android.bp"中将aidl文件加入编译链
//...省略
"core/java/android/app/IAlarmListener.aidl",
"core/java/android/app/IAlarmManager.aidl",
///ADD START
"core/java/android/app/util/IFileManager.aidl",
///ADD END
"core/java/android/app/IAppTask.aidl",
"core/java/android/app/IApplicationThread.aidl",
//...省略
创建FileManager.java
// frameworks/base/core/java/android/app/util
package android.app.util;
import android.content.Context;
import android.util.Log;
public class FileManager {
private final IFileManager mFileManager;
private Context mContext;
public FileManager(Context c, IFileManager f){
mFileManager = f;
mContext = c;
}
public String readFile(String path){
try{
return mFileManager.readFile(path);
} catch (Exception e){
Log.e("yooha-log", "FileManager -> readFile:" + e.toString());
}
return null;
}
public void writeFile(String path,String data){
try{
mFileManager.writeFile(path, data);
} catch (Exception e){
Log.e("yooha-log", "FileManager -> writeFile:" + e.toString());
}
}
public String shellExec(String cmd){
try{
return mFileManager.shellExec(cmd);
} catch (Exception e){
Log.e("yooha-log", "FileManager -> shellExec:" + e.toString());
}
return null;
}
}
package com.android.server;
import android.app.util.IFileManager;
import android.content.Context;
import android.util.Log;
import java.io.BufferedReader;
import java.io.File;
import java.lang.YLog;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
public class FileManagerService extends IFileManager.Stub{
private Context mContext;
public FileManagerService(Context context){
super();
mContext = context;
Log.i("yooha-log", "FileManagerService init");
}
public String readFile(String path){
YLog.info("FileManagerService->readFile:" + path);
File file = new File(path);
StringBuilder sb=new StringBuilder();
if (file != null && file.exists()) {
InputStream inputStream = null;
BufferedReader bufferedReader = null;
try {
inputStream = new FileInputStream(file);
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String outData;
while((outData=bufferedReader.readLine())!=null){
sb.append(outData+"\n");
}
YLog.info("FileManagerService->readFile string:" + sb.toString());
} catch (Exception e) {
YLog.error(e.toString());
} finally {
try {
if (bufferedReader != null) {
bufferedReader.close();
}
} catch (Exception e) {
YLog.error(e.toString());
}
try {
if (inputStream != null) {
inputStream.close();
}
} catch (Exception e) {
YLog.error(e.toString());
}
}
}else{
YLog.error("open file error:" + path);
}
return sb.toString();
}
public void writeFile(String filePath,String strcontent){
String strFilePath = filePath;
String strContent = strcontent + "\n"; // \r\n 结尾会变成 ^M
try {
File file = new File(strFilePath);
createFile(file.getParent(),file.getName());
if (!file.exists()) {
file.getParentFile().mkdirs();
file.createNewFile();
}
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
raf.setLength(0);
// 写文件的位置标记,从文件开头开始,后续读取文件内容从该标记开始
long writePosition = raf.getFilePointer();
raf.seek(writePosition);
raf.write(strContent.getBytes());
raf.close();
//
} catch (Exception e) {
YLog.error( "FileManagerService->writeFile:" + e);
}
}
// create file
private void createFile(String filePath, String fileName) {
createDirectory(filePath);
try {
File file = new File(filePath +"/"+ fileName);
if (!file.exists()) {
file.createNewFile();
}
} catch (Exception e) {
YLog.error( "FileManagerService->createFile:" + e);
}
}
// create dir
private void createDirectory(String filePath) {
File file = null;
try {
file = new File(filePath);
if (!file.exists()) {
boolean isok= file.mkdir();
}
} catch (Exception e) {
YLog.error( "FileManagerService->createDirectory:" + e);
}
}
public String shellExec(String cmd){
return null;
}
}
// frameworks\base\core\java\android\content\Context.java
///ADD START
public static final String FILE_MANAGER_SERVICE = "file_manager";
///ADD END
// frameworks\base\services\java\com\android\server\SystemServer.java
//add start
try{
traceBeginAndSlog("StartFileManagerService");
FileManagerService fileManagerService = new FileManagerService(context);
ServiceManager.addService(Context.FILE_MANAGER_SERVICE, fileManagerService);
traceEnd();
} catch (Exception e){
Slog.i("yooha-log", "SystemServer add service:" + e);
}
//add end
// frameworks/base/core/java/android/app/SystemServiceRegistry.java
///add start
registerService(Context.FILE_MANAGER_SERVICE, FileManager.class,
new CachedServiceFetcher() {
@Override
public FileManager createService(ContextImpl ctx) throws ServiceNotFoundException{
try{
IBinder b = ServiceManager.getService(Context.FILE_MANAGER_SERVICE);
return b == null ? null : new FileManager(ctx, IFileManager.Stub.asInterface(b));
} catch (Exception e){
Log.e("yooha-log", "SystemServiceRegistry :" + e.toString());
return null;
}
}});
///add end
package android.app.util;
import android.content.Context;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import java.lang.YLog;
public class IOUtil {
private static IFileManager ifm = null;
public static IFileManager getInstence(){
if (ifm == null){
IBinder binder = ServiceManager.getService(Context.FILE_MANAGER_SERVICE);
if(binder==null){
YLog.error("IOUtil->getInstence:getInstence binder is null");
return ifm;
}
ifm = IFileManager.Stub.asInterface(binder);
}
//Log.i("yooha-log","IOUtil->getInstence:getInstence binder is:" + ifm.toString());
return ifm;
}
public static String getConfig(){
try{
IFileManager fm = getInstence();
if (fm == null){
return "";
}
return fm.readFile("/sdcard/yooha.conf");
} catch (Exception e){
YLog.error(e.toString());
}
return "";
}
}
需配置的文件及路径
---system
|sepilicy
|-public
|service.te
|untrust_app.te
|-private
|service_contexts
|-prebuilts
|-api
|-26
|-public
|service.te
|-private
|service_contexts
|-27
|-public
|service.te
|-private
|service_contexts
|-28
|-public
|service.te
|-private
|service_contexts
|-29
|-public
|service.te
|untrust_app.te
|-private
|service_contexts
service.te
# ///ADD START
type file_manager_service, app_api_service, system_api_service, system_server_service, service_manager_type;
# ///ADD END
service_contexts
# ///ADD START
file_manager u:object_r:file_manager_service:s0
# ///ADD END
编译刷机后,发现出现文件读写权限异常,如下:
后来查了一下PID:1274 为system_server,系统服务居然不能读写sdcard。后来查阅资料才知道,system_server确实不能读写sdcard。因为:以前的sdcard卡是可插拔的,如果系统进程去访问sdcard目录,会持有sdcard文件句柄,如果sdcard被拔除,可能会导致系统崩溃,故官方做了这个限制。
下面着手解决这个问题,先看一下sdcard的分组,如下为sdcard_rw
rk3288:/sdcard $ ll
total 35M
drwxrwx--x 20 root sdcard_rw 4.0K 2022-02-18 10:52 .
drwx--x--x 4 root sdcard_rw 4.0K 2021-12-27 11:40 ..
drwxrwx--x 2 root sdcard_rw 4.0K 2021-12-27 11:40 Alarms
drwxrwx--x 3 root sdcard_rw 4.0K 2021-12-27 11:40 Android
drwxrwx--x 2 root sdcard_rw 4.0K 2022-01-18 15:30 CircleDetector
drwxrwx--x 4 root sdcard_rw 4.0K 2021-12-30 09:35 DCIM
drwxrwx--x 2 root sdcard_rw 4.0K 2021-12-27 11:40 Download
drwxrwx--x 2 root sdcard_rw 4.0K 2021-12-27 11:40 Movies
drwxrwx--x 2 root sdcard_rw 4.0K 2021-12-27 11:40 Music
drwxrwx--x 2 root sdcard_rw 4.0K 2021-12-27 11:40 Notifications
drwxrwx--x 2 root sdcard_rw 4.0K 2021-12-27 11:40 Pictures
drwxrwx--x 2 root sdcard_rw 4.0K 2021-12-27 11:40 Podcasts
drwxrwx--x 2 root sdcard_rw 4.0K 2021-12-27 11:40 Ringtones
查看分组的具体编号
android/system/core/include/private/android_filesystem_config.h
sdcard_rw 分组为1015,再看看 system_server 的group(下图中的1015为我后来添加进去的,修改之前并无1015)
添加1015(在ZygoteInit.java中startSystemServer中会设置系统服务启动参数,在--setgroups之后添加1015)
/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
/* Hardcoded command line to start the system server */
String args[] = {
"--setuid=1000",
"--setgid=1000",
"--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1015,1018,1021,1032,3001,3002,3003,3006,3007",
"--capabilities=" + capabilities + "," + capabilities,
"--nice-name=system_server",
"--runtime-args",
"com.android.server.SystemServer",
};
验证添加结果:(添加之后还是报权限问题,这里就不贴图了)
查看一下system_server发现1015确实已经添加进去,继续排查问题吧。
再来排查一下是否确为selinux导致的这个问题,先关掉selinux试试
然后,在测试随便打开一个应用
发现可以正确读取到sdcard下的文件了。说明以上问题确实为selinux导致。
接下来,查看内核日志
发现是system_server 对 sdcardfs 的file对象 缺少read权限,于是进行修改
-|system +
|sepolicy|
|private
|system_server.te
修改:allow system_server sdcard_type:dir { getattr search }; 为 allow system_server sdcard_type:dir { create_dir_perms rw_file_perms};
修改:allow system_server configfs:file { getattr open create unlink write }; 为 allow system_server configfs:file { getattr open create unlink write read };
添加:allow system_server sdcard_type:file { create_file_perms rw_file_perms };
删除:neverallow system_server sdcard_type:dir { open read write };
删除:neverallow system_server sdcard_type:file rw_file_perms;
|prebuilts|
|api|
|26.0|
|private|
|system_server.te //修改同上
|27.0|
|private|
|system_server.te //修改同上
|28.0|
|private|
|system_server.te //修改同上
|29.0|
|private|
|system_server.te //修改同上,此文件必须与/sepolicy/private/system_server.te完全一致
然后编译刷机,测试,发现还是无法读取。继续看内核日志:
这次报 /data/media/0/yooha.conf 路径 open权限问题,继续在system_server.te中对media_rw_data_file:file添加open权限
allow system_server media_rw_data_file:file { getattr read write append open };
继续编译、刷机、测试。成功读取到了配置信息。其实将配置信息放在/system/下的话,system_server是可以直接读取的,不用这么麻烦到修改selinux配置。但考虑到后期,编译user版本时,由外部push配置信息到sdcard下的场景,所以这次就一次搞到位,为以后省去麻烦