最近因业务需要,在安卓10源码中添加了一个文件读写服务,最后发现莫名出现sdcard读写权限问题,我默认为系统服务肯定是能读写sdcard,结果发现其实不是,下面记录一下问题过程和解决方法

一、添加系统服务

创建 IFileManager.aidl

// 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;
    }
}

创建 FileManagerService.java

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;
    }

}

在"Context.java"文件中添加服务名称

// frameworks\base\core\java\android\content\Context.java

///ADD START
public static final String FILE_MANAGER_SERVICE = "file_manager";
///ADD END

在SystemServer类中的startOtherServices函数注册 FileManagerService

// 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

绑定 FileManagerService 和 FileManager

// frameworks/base/core/java/android/app/SystemServiceRegistry.java

///add start

registerService(Context.FILE_MANAGER_SERVICE, FileManager.class,
        new CachedServiceFetcher<FileManager>() {
            @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 "";
    }


}

二、为新增的service配置selinux策略

需配置的文件及路径
---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下的场景,所以这次就一次搞到位,为以后省去麻烦

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐