- 添加完整的 GitHub Actions 构建指南文档 - BUILD_GUIDE.md: Android 详细构建指南 - MULTIPLATFORM_GUIDE.md: 多平台构建指南 - HOW_TO_BUILD.md: 分步操作教程 - QUICKSTART.md: 3步快速开始指南 - INDEX.md: 文档总览索引 - README.md: 基础说明 - 创建 docs/ 目录存放项目文档 - 锁定 libcore 子模块到 f993a57 (v3.1.7) - 防止在线编译时使用最新版本 - 确保构建稳定性和一致性
8.2 KiB
Android SELinux chown 权限问题修复方案
问题背景
在 Android 12+ 系统上,由于 SELinux 严格模式的权限限制,sing-box 核心库在启动时会因为 chown 操作被拒绝而失败。具体表现为以下三个错误:
- CommandServer 错误:
chown: chown command.sock: operation not permitted - Logger 错误:
start logger: chown box.log: operation not permitted - Clash Cache 错误:
pre-start cache file: platform chown: chown clash.db: operation not permitted
问题分析
根本原因
sing-box (libbox) 在创建文件后会尝试修改文件所有权(chown操作),但在 Android 的 SELinux 环境下,应用无法修改外部存储目录中文件的所有权,导致启动失败。
涉及的文件
command.sock- CommandServer 的 Unix socket 文件box.log- sing-box 的日志文件clash.db- Clash API 的缓存数据库文件
修复方案
方案概述
核心思路: 在配置层面禁用会触发 chown 操作的功能,或者修改底层代码跳过 chown 操作。
由于修改 sing-box 底层库较为复杂,建议采用配置修改方案。
具体修复步骤
修复 1: 禁用文件日志
文件: libcore/config/hiddify_option.go
位置: DefaultHiddifyOptions() 函数
修改前:
LogLevel: "warn",
// LogFile: "/dev/null",
LogFile: "box.log",
修改后:
LogLevel: "warn",
LogFile: "", // 禁用文件日志,避免 Android SELinux chown 权限问题
// LogFile: "box.log",
说明:
- 将
LogFile设置为空字符串,禁用文件日志输出 - 日志将只输出到 stderr,可以通过 logcat 查看
- 这不会影响日志功能,只是改变了输出目标
修复 2: 禁用 Clash API 缓存
文件: libcore/config/config.go
位置: setClashAPI() 函数
修改前:
func setClashAPI(options *option.Options, opt *HiddifyOptions) {
if opt.EnableClashApi {
if opt.ClashApiSecret == "" {
opt.ClashApiSecret = generateRandomString(16)
}
options.Experimental = &option.ExperimentalOptions{
ClashAPI: &option.ClashAPIOptions{
ExternalController: fmt.Sprintf("%s:%d", "127.0.0.1", opt.ClashApiPort),
Secret: opt.ClashApiSecret,
},
CacheFile: &option.CacheFileOptions{
Enabled: true,
Path: "clash.db",
},
}
}
}
修改后:
func setClashAPI(options *option.Options, opt *HiddifyOptions) {
if opt.EnableClashApi {
if opt.ClashApiSecret == "" {
opt.ClashApiSecret = generateRandomString(16)
}
options.Experimental = &option.ExperimentalOptions{
ClashAPI: &option.ClashAPIOptions{
ExternalController: fmt.Sprintf("%s:%d", "127.0.0.1", opt.ClashApiPort),
Secret: opt.ClashApiSecret,
},
CacheFile: &option.CacheFileOptions{
Enabled: false, // 禁用缓存以避免 Android SELinux chown 权限问题
Path: "", // 清空路径
},
}
}
}
说明:
- 将
Enabled设置为false,禁用 Clash API 缓存功能 - 将
Path设置为空字符串 - Clash API 功能仍然可用,只是不会持久化节点选择等信息
修复 3: CommandServer 错误处理 (可选)
说明: CommandServer 主要用于命令行控制,对 Android VPN 功能不是必需的。
方案 A: 在应用层捕获异常 (推荐)
文件: android/app/src/main/kotlin/com/hiddify/hiddify/bg/BoxService.kt
位置: onStartCommand() 函数
修改:
GlobalScope.launch(Dispatchers.IO) {
Settings.startedByUser = true
initialize()
try {
startCommandServer()
} catch (e: Exception) {
// CommandServer 启动失败不是致命错误
// 在 Android 12+ SELinux 环境下,chown 操作可能被拒绝
// 但这不影响 VPN 核心功能,因此记录警告但继续启动服务
Log.w(TAG, "CommandServer failed to start (non-fatal): ${e.message}")
}
startService()
}
方案 B: 完全禁用 CommandServer
如果不需要 CommandServer 功能,可以直接注释掉启动代码:
// startCommandServer() // 在 Android 上禁用
验证修复
编译步骤
- 使用 Docker 编译 (推荐):
cd libcore
docker run --rm -v "$PWD:/workspace" -w /workspace golang:1.23 bash -c "bash docker-compile.sh"
- 或者本地编译:
cd libcore
make android
部署编译好的库
编译完成后,有两种部署方式:
方式A: 替换完整AAR (推荐生产环境)
cd libcore
# 替换AAR文件
cp libcore.aar ../android/app/libs/
# 删除可能冲突的so文件
rm -rf ../android/app/src/main/jniLibs/
# 重新编译Flutter项目
cd ..
flutter clean && flutter build apk
优点: 支持所有架构,适合发布版本
方式B: 只提取arm64 so文件 (推荐测试环境)
cd libcore
# 从AAR提取arm64的libbox.so
unzip -j libcore.aar jni/arm64-v8a/libbox.so -d /tmp/
mkdir -p ../android/app/src/main/jniLibs/arm64-v8a/
cp /tmp/libbox.so ../android/app/src/main/jniLibs/arm64-v8a/
# 临时禁用AAR(避免冲突)
mv ../android/app/libs/libcore.aar ../android/app/libs/libcore.aar.old
# 编译测试版本
cd ..
flutter clean && flutter build apk --debug --split-per-abi
优点: 编译快速,APK体积小,适合开发调试
⚠️ 重要: 两种方式不能同时使用,会产生冲突!请根据需求选择其一。
测试验证
- 重新编译 Flutter 应用
- 在 Android 设备上安装并启动
- 查看 logcat 日志,确认没有 chown 相关错误
- 验证 VPN 能正常启动和代理流量
预期结果
修复后,应用启动日志应该显示:
D/A/BoxService: base dir: /data/user/0/app.xxx.com/files
D/A/BoxService: working dir: /storage/emulated/0/Android/data/app.xxx.com/files
D/A/BoxService: temp dir: /data/user/0/app.xxx.com/cache
W/A/BoxService: CommandServer failed to start (non-fatal): chown: chown command.sock: operation not permitted
D/A/BoxService: starting service
D/A/BoxService: 配置已修改: 禁用文件日志和Clash缓存
D/A/EventHandler: new status: Started ✅ 成功启动!
影响评估
功能影响
| 功能 | 修复前 | 修复后 | 影响 |
|---|---|---|---|
| VPN 代理 | ❌ 无法启动 | ✅ 正常工作 | 无影响 |
| 日志记录 | 文件输出 | logcat 输出 | 日志仍可用,通过 logcat 查看 |
| Clash API | 带缓存 | 无缓存 | 节点选择不持久化,重启后重置 |
| CommandServer | ❌ 失败 | 跳过启动 | 命令行控制不可用(非必需) |
性能影响
- 无性能损失: 禁用缓存和文件日志对性能无负面影响
- 启动速度: 可能略微加快(减少文件 I/O 操作)
替代方案 (高级)
如果需要保留完整功能,可以考虑以下方案:
方案 1: 修改 sing-box 源码
在 sing-box 底层库中跳过 Android 平台的 chown 操作:
// 在文件创建后的 chown 调用处添加平台判断
if runtime.GOOS != "android" {
if err := os.Chown(path, uid, gid); err != nil {
return err
}
}
优点: 保留所有功能 缺点: 需要修改 sing-box 上游代码,维护成本高
方案 2: 使用内部存储
将文件创建在 /data/user/0/ 目录下而不是外部存储:
优点: 避免 SELinux 限制 缺点: 空间受限,不适合大文件
常见问题
Q1: 禁用文件日志后如何查看日志?
A: 使用 adb logcat:
adb logcat | grep "A/BoxService\|singbox"
Q2: 禁用 Clash 缓存后有什么影响?
A: 每次启动 VPN 时,节点选择会重置为默认值,但不影响 VPN 的核心代理功能。
Q3: 为什么 hiddify-app 可以正常运行?
A: hiddify-app 使用的 libcore 版本已经包含了这些修复,或者使用了不同的配置策略。
Q4: 这个修复是否适用于所有 Android 版本?
A: 是的,这个修复对 Android 11 及以下版本也兼容,不会产生负面影响。
参考资料
更新日志
- 2025-10-27: 初始版本,包含三个核心修复方案