实现功能:开机时自动执行set_xxx.sh脚本,把system/yyy 目录下的 zzz.db 文件复制到 data/data/com.android.ppp/databases 文件夹下(xxx、yyy、zzz、ppp为举例用,增加替换成自己的名字)
第一步:
新建set_xxx.sh脚本,内容如下(脚本执行log通过echo输出内容到data/zzz_log.txt,调试完建议注释掉)
#!/system/bin/sh
Top_Path="/"
System_Path="${Top_Path}system"
Data_Path="${Top_Path}data"
NewDatabase="${System_Path}/vendor/yyy/zzz.db"
NewDatabaseJournal="${System_Path}/vendor/yyy/zzz.db-journal"
Database_Dir="${Data_Path}/data/com.android.ppp/databases"
Database="${Database_Dir}/zzz.db"
DatabaseJournal="${Database_Dir}/zzz.db-journal"
Log_file="${Data_Path}/copy_db_log.txt"
newDbMd5="1"
preDbMd5="0"
#判断存放log的文件是否存在,不存在就新建
if [ ! -f "/data/$Log_file" ];then
cd $Data_Path
touch $Data_Path/copy_db_log.txt
cd $Top_Path
echo "-----------copy db-----------" > $Log_file
echo "Log_file is no exit, make it." >> $Log_file
fi
#判断数据库所在路径中的文件夹是否存在,不存在就新建
if [ -d $Database_Dir ];then
echo "Database_Dir is exit" >> $Log_file
else
echo "Database_Dir is not exit, mkdir..." >> $Log_file
if [ ! -d $Data_Path ]; then
cd $Top_Path
mkdir -p "data"
fi
cd $Data_Path
mkdir -p "data/com.android.ppp/databases"
fi
#判断数据库文件是否存在,存在就删除
if [ -f $Database ];then
echo "Database database is exit" >> $Log_file
rm -rf ${Database}
echo "${Database} database is removed" >> $Log_file
fi
echo "Database database is not exit, it will cp!" >> $Log_file
i=0
#进行拷贝数据库操作,拷贝成功会跳出循环,失败就循环执行,尝试3次
while ((i<3))
do
cp -f $NewDatabase $Database
sync
cp -f $NewDatabaseJournal $DatabaseJournal
sync
preDbMd5=$(md5sum $Database | busybox awk '{print $1}')
newDbMd5=$(md5sum $NewDatabase | busybox awk '{print $1}')
echo "preDbMd5="$preDbMd5" newDbMd5="$newDbMd5" is copy finish!" >> $Log_file
if [ "$newDbMd5" == "$preDbMd5" ];then
echo "Database copy success!" >> $Log_file
chmod 771 $Database_Dir
chmod 660 $Database
chmod 400 $NewDatabaseJournal
#这里system:system是文件所有者和所在组,要与文件夹保持一致,可以在开机时用adb进入ls -al查看文件夹
chown -R system:system $Database_Dir
break
fi
echo "Database while do "$i" >>>>" >> $Log_file
let i++
echo "Database copying sleep 0.5 >>>>" >> $Log_file
sleep 0.5
done
echo "Database copy done, ok!!!" >> $Log_file
第二步:
进入自己的项目路径,例如:device/board/project(board板型或方案,project具体项目),找到sepolicy文件夹,project文件夹中没有就到device/下面的common中,反正找到自己项目用的sepolicy目录,在目录下file_contexts文件中,在最后添加:
# add
/system/bin/set_xxx.sh u:object_r:set_xxx_exec:s0
意思是将/system/bin/set_xxx.sh文件在打包system.img时设置安全上下文为u:object_r:set_xxx_exec:s0,用于保护其不被别的进程可以随意调用执行/system/bin/set_xxx.sh,只有具有权限执行type为set_xxx_exec的进程才可以执行set_xxx.sh。
第三步:
为了使其能够添加到servicemanager中去并且能够访问系统资源,我们还要再在sepolicy目录下添加set_xxx.te文件,在文件里面添加脚本要用到的权限,内容如下:
##############################
# add
#
# set_xxx sh selinux
##############################
#设置set_xxx属于domain域,是一个进程的type;mlstrustedsubject设置shelld是一个可信任的主题
type set_xxx, domain, mlstrustedsubject;
#设置set_xxx_exec属于可执行文件的类型
type set_xxx_exec, exec_type, file_type;
#for debug,下面这行permissive是在不知道具体要哪些权限的时候打开,用来暂时获得所有权限,
#具体需要的权限可以通过logcat里的提示逐一添加,添加完权限把这里注释掉
#permissive set_xxx;
init_daemon_domain(set_xxx)
#具体需要的权限:
allow set_xxx shell_exec:file { entrypoint read };
allow set_xxx self:capability { dac_override chown };
allow set_xxx system_data_file:dir { write add_name create add_name setattr read open };
allow set_xxx system_data_file:file { create open write append setattr };
allow set_xxx system_file:file { execute_no_trans };
allow set_xxx self:process execmem;
查看需要添加哪些权限的方法,先用上面说的permissive暂时获得所有权限,然后抓logcat,搜关键字avc,类似下面的log:
11-18 19:01:38.970 14835 14835 W cp : type=1400 audit(0.0:16179): avc: denied { write } for name="com.android.inputmethod" dev="mmcblk0p20" ino=113346 scontext=u:r:set_xxx:s0 tcontext=u:object_r:system_data_file:s0:c512,c768 tclass=file permissive=0
这条log可以大概解读为在执行cp 拷贝命令的时候,set_xxx需要system_data_file的file文件类型的wirte权限,也就是:
allow set_xxx system_data_file:file { write };
其它的类似逐个添加上就行,全部添加完,确认logcat中没有再提示用到哪些权限,就可以注释掉permissive这一行。
注意:如果编译的时候提示有的权限不被允许添加使用,可以在android/external/sepolicy/domain.te文件中,找到对应权限限制的地方,排除掉我们的sh的限制,比如:
neverallow {
domain
-recovery
-system_server
-system_app
-init
-rild
-radio_config
-emsd
-lc-elog
-lc-dump
-lc-poweron-log
-lc-oms-mla
-lc-mla-manager
-lc-oms-amt
-amt-local
-installd # for relabelfrom and unlink, check for this in explicit neverallow
-lc-oms-sa
-logd
-factory
-akmd09911
-set_xxx #add 排除system_data_file:file不能写的系统限制
} system_data_file:file no_w_file_perms;
第四步:
在init.rc文件中添加如下内容:
#add
on property:persist.sys.cpdb=0
stop set_xxx
on property:persist.sys.cpdb=1
start set_xxx
service set_xxx /system/bin/sh /system/bin/set_xxx.sh
user root
group root
oneshot
seclabel u:r:set_xxx:s0
通过改变属性persist.sys.cpdb的值来改变是否后续开机需要启动该脚本service。
可能碰到开机的时候sh脚本先start执行,然后再stop的问题,这会造成cp的db文件已开始拷贝,但是因为没拷贝完不能用,只需要在sh文件中执行前添加上sleep 零点几秒即可,如下
#!/system/bin/sh
#加上0.4s的sleep,防止先start执行脚本,再stop
sleep 0.4
Top_Path="/"
System_Path="${Top_Path}system"
Data_Path="${Top_Path}data"
第五步:
在具体的项目的device.mk中添加如下内容:
#add
PRODUCT_PROPERTY_OVERRIDES += \
persist.sys.cpdb=1
到此,我们添加的sh文件service服务就能正常执行启动了,上层源码可以通过如下方法控制开启/关闭:
//获取属性的值
int sp = SystemProperties.getInt("persist.sys.cpdb",1);
//设置属性的值,控制开关
SystemProperties.set("persist.sys.cpdb","0");
记录以备下次用,如有错误,欢迎留意指正,谢谢!
参考:
Android 6.0中SELinux的TE简介
init.rc中添加服务以在开机时启动脚本
RK3399 Android 7.1.2 添加.sh的开机服务
--------------------------2021.9.8 更新 分割线----------------------------
主体(Subject):等同于进程。
目标/对象(Target/Object):被主体访问的资源,如文件、设备、端口等等,也可以称呼客体。
上面的log:
11-18 19:01:38.970 14835 14835 W cp : type=1400 audit(0.0:16179): avc: denied { write } for name="com.android.inputmethod" dev="mmcblk0p20" ino=113346 scontext=u:r:set_xxx:s0 tcontext=u:object_r:system_data_file:s0:c512,c768 tclass=file permissive=0
这条log可以重新解读为:
执行cp 拷贝命令的时候,主体上下文 set_xxx 需要目标客体上下文 system_data_file 的 file 文件类型的 wirte 权限。
ps -Z 查看主体安全上下文
ls -Z 查看客体安全上下文
参考:
SELinux MAC安全机制简介_林多
Android-SEAndroid权限问题指南_IT先森