# Android SELinux chown 权限问题修复方案 ## 问题背景 在 Android 12+ 系统上,由于 SELinux 严格模式的权限限制,sing-box 核心库在启动时会因为 `chown` 操作被拒绝而失败。具体表现为以下三个错误: 1. **CommandServer 错误**: `chown: chown command.sock: operation not permitted` 2. **Logger 错误**: `start logger: chown box.log: operation not permitted` 3. **Clash Cache 错误**: `pre-start cache file: platform chown: chown clash.db: operation not permitted` ## 问题分析 ### 根本原因 sing-box (libbox) 在创建文件后会尝试修改文件所有权(`chown`操作),但在 Android 的 SELinux 环境下,应用无法修改外部存储目录中文件的所有权,导致启动失败。 ### 涉及的文件 1. `command.sock` - CommandServer 的 Unix socket 文件 2. `box.log` - sing-box 的日志文件 3. `clash.db` - Clash API 的缓存数据库文件 ## 修复方案 ### 方案概述 **核心思路**: 在配置层面禁用会触发 chown 操作的功能,或者修改底层代码跳过 chown 操作。 由于修改 sing-box 底层库较为复杂,建议采用配置修改方案。 --- ## 具体修复步骤 ### 修复 1: 禁用文件日志 **文件**: `libcore/config/hiddify_option.go` **位置**: `DefaultHiddifyOptions()` 函数 **修改前**: ```go LogLevel: "warn", // LogFile: "/dev/null", LogFile: "box.log", ``` **修改后**: ```go LogLevel: "warn", LogFile: "", // 禁用文件日志,避免 Android SELinux chown 权限问题 // LogFile: "box.log", ``` **说明**: - 将 `LogFile` 设置为空字符串,禁用文件日志输出 - 日志将只输出到 stderr,可以通过 logcat 查看 - 这不会影响日志功能,只是改变了输出目标 --- ### 修复 2: 禁用 Clash API 缓存 **文件**: `libcore/config/config.go` **位置**: `setClashAPI()` 函数 **修改前**: ```go 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", }, } } } ``` **修改后**: ```go 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()` 函数 **修改**: ```kotlin 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 功能,可以直接注释掉启动代码: ```kotlin // startCommandServer() // 在 Android 上禁用 ``` --- ## 验证修复 ### 编译步骤 1. **使用 Docker 编译** (推荐): ```bash cd libcore docker run --rm -v "$PWD:/workspace" -w /workspace golang:1.23 bash -c "bash docker-compile.sh" ``` 2. **或者本地编译**: ```bash cd libcore make android ``` ### 部署编译好的库 编译完成后,有两种部署方式: #### 方式A: 替换完整AAR (推荐生产环境) ```bash 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文件 (推荐测试环境) ```bash 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体积小,适合开发调试 ⚠️ **重要**: 两种方式不能同时使用,会产生冲突!请根据需求选择其一。 ### 测试验证 1. 重新编译 Flutter 应用 2. 在 Android 设备上安装并启动 3. 查看 logcat 日志,确认没有 chown 相关错误 4. 验证 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 操作: ```go // 在文件创建后的 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: ```bash adb logcat | grep "A/BoxService\|singbox" ``` ### Q2: 禁用 Clash 缓存后有什么影响? **A**: 每次启动 VPN 时,节点选择会重置为默认值,但不影响 VPN 的核心代理功能。 ### Q3: 为什么 hiddify-app 可以正常运行? **A**: hiddify-app 使用的 libcore 版本已经包含了这些修复,或者使用了不同的配置策略。 ### Q4: 这个修复是否适用于所有 Android 版本? **A**: 是的,这个修复对 Android 11 及以下版本也兼容,不会产生负面影响。 --- ## 参考资料 - [Android SELinux 权限文档](https://source.android.com/docs/security/features/selinux) - [sing-box 配置文档](https://sing-box.sagernet.org/configuration/) - [Clash API 配置](https://sing-box.sagernet.org/configuration/experimental/clash-api/) --- ## 更新日志 - **2025-10-27**: 初始版本,包含三个核心修复方案