Keep HEV credentials out of root scripts (#6138)
This commit is contained in:
1 parent
e27941558e
commit
11b5784321
2 files changed
+60
-13
No files matched your search
@@ -2,6 +2,7 @@ package com.v2ray.ang.root
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Process
|
import android.os.Process
|
||||||
|
import android.util.AtomicFile
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.handler.MmkvManager
|
import com.v2ray.ang.handler.MmkvManager
|
||||||
import com.v2ray.ang.handler.SettingsManager
|
import com.v2ray.ang.handler.SettingsManager
|
||||||
@@ -112,15 +113,25 @@ object RootProxyManager {
|
|||||||
val socksUsername = SettingsManager.getSocksUsername()
|
val socksUsername = SettingsManager.getSocksUsername()
|
||||||
val socksPassword = SettingsManager.getSocksPassword()
|
val socksPassword = SettingsManager.getSocksPassword()
|
||||||
val port = SettingsManager.getSocksPort()
|
val port = SettingsManager.getSocksPort()
|
||||||
val runDir = File(context.filesDir, AppConfig.ROOT_RUNTIME_DIR).apply { mkdirs() }
|
val runDir = File(context.filesDir, AppConfig.ROOT_RUNTIME_DIR)
|
||||||
|
if (!runDir.isDirectory && !runDir.mkdirs()) {
|
||||||
|
LogUtil.e(AppConfig.TAG, "RootProxyManager: failed to create runtime directory at ${runDir.absolutePath}")
|
||||||
|
return null
|
||||||
|
}
|
||||||
val pidFile = File(runDir, "tun2socks.pid").absolutePath
|
val pidFile = File(runDir, "tun2socks.pid").absolutePath
|
||||||
val logFile = File(runDir, "tun2socks.log").absolutePath
|
val logFile = File(runDir, "tun2socks.log").absolutePath
|
||||||
val cfgFile = File(runDir, "tun2socks.yml").absolutePath
|
val cfgFile = File(runDir, "tun2socks.yml")
|
||||||
val oomGuardPid = File(runDir, "oomguard.pid").absolutePath
|
val oomGuardPid = File(runDir, "oomguard.pid").absolutePath
|
||||||
val ipv6 = MmkvManager.decodeSettingsBool(AppConfig.PREF_IPV6_ENABLED)
|
val ipv6 = MmkvManager.decodeSettingsBool(AppConfig.PREF_IPV6_ENABLED)
|
||||||
val lanShare = forceLanShare || MmkvManager.decodeSettingsBool(AppConfig.PREF_ROOT_LAN_SHARING)
|
val lanShare = forceLanShare || MmkvManager.decodeSettingsBool(AppConfig.PREF_ROOT_LAN_SHARING)
|
||||||
val corePid = Process.myPid()
|
val corePid = Process.myPid()
|
||||||
|
|
||||||
|
val config = buildHevConfig(socksUsername, socksPassword, port, ipv6)
|
||||||
|
if (!writeHevConfig(cfgFile, config)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val cfgPath = cfgFile.absolutePath
|
||||||
|
|
||||||
// Per-app proxy/bypass (mirrors what VpnService does via allowed/disallowed apps).
|
// Per-app proxy/bypass (mirrors what VpnService does via allowed/disallowed apps).
|
||||||
val perAppEnabled = MmkvManager.decodeSettingsBool(AppConfig.PREF_PER_APP_PROXY)
|
val perAppEnabled = MmkvManager.decodeSettingsBool(AppConfig.PREF_PER_APP_PROXY)
|
||||||
val bypassApps = MmkvManager.decodeSettingsBool(AppConfig.PREF_BYPASS_APPS)
|
val bypassApps = MmkvManager.decodeSettingsBool(AppConfig.PREF_BYPASS_APPS)
|
||||||
@@ -141,15 +152,9 @@ object RootProxyManager {
|
|||||||
appendLine("echo \$! > '$oomGuardPid'")
|
appendLine("echo \$! > '$oomGuardPid'")
|
||||||
// tun device node
|
// tun device node
|
||||||
appendLine("if [ ! -e /dev/net/tun ]; then mkdir -p /dev/net; mknod /dev/net/tun c 10 200; chmod 666 /dev/net/tun; fi")
|
appendLine("if [ ! -e /dev/net/tun ]; then mkdir -p /dev/net; mknod /dev/net/tun c 10 200; chmod 666 /dev/net/tun; fi")
|
||||||
// hev-socks5-tunnel config: it creates the tun ($TUN) itself and forwards it to the
|
// The HEV config is written separately by the app process. Never place credentials
|
||||||
// in-process core's SOCKS inbound on loopback. MTU comes from the existing VPN MTU
|
// in this root shell script, even when YAML quoting would otherwise be valid.
|
||||||
// setting. No fwmark on hev's sockets: its only upstream connection is to 127.0.0.1
|
appendLine("nohup \"\$BIN\" '$cfgPath' >'$logFile' 2>&1 &")
|
||||||
// (loopback, already RETURNed by the 127.0.0.0/8 bypass) and the core's real outbound
|
|
||||||
// runs as the app uid (RETURNed by the uid-owner rule), so traffic can't loop.
|
|
||||||
appendLine("cat > '$cfgFile' <<'HEVCFG'")
|
|
||||||
append(buildHevConfig(socksUsername = socksUsername, socksPassword = socksPassword, socksPort = port, ipv6 = ipv6))
|
|
||||||
appendLine("HEVCFG")
|
|
||||||
appendLine("nohup \"\$BIN\" '$cfgFile' >'$logFile' 2>&1 &")
|
|
||||||
appendLine("T2S_PID=\$!")
|
appendLine("T2S_PID=\$!")
|
||||||
appendLine("echo \$T2S_PID > '$pidFile'")
|
appendLine("echo \$T2S_PID > '$pidFile'")
|
||||||
appendLine("echo ${AppConfig.ROOT_OOM_SCORE} > /proc/\$T2S_PID/oom_score_adj 2>/dev/null || true")
|
appendLine("echo ${AppConfig.ROOT_OOM_SCORE} > /proc/\$T2S_PID/oom_score_adj 2>/dev/null || true")
|
||||||
@@ -212,13 +217,33 @@ object RootProxyManager {
|
|||||||
appendLine(" address: '${AppConfig.LOOPBACK}'")
|
appendLine(" address: '${AppConfig.LOOPBACK}'")
|
||||||
appendLine(" udp: 'udp'")
|
appendLine(" udp: 'udp'")
|
||||||
if (socksUsername != null && socksPassword != null) {
|
if (socksUsername != null && socksPassword != null) {
|
||||||
appendLine(" username: '$socksUsername'")
|
appendLine(" username: ${socksUsername.toSingleQuotedYamlScalar()}")
|
||||||
appendLine(" password: '$socksPassword'")
|
appendLine(" password: ${socksPassword.toSingleQuotedYamlScalar()}")
|
||||||
}
|
}
|
||||||
appendLine(" tcp-fastopen: true")
|
appendLine(" tcp-fastopen: true")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun writeHevConfig(file: File, config: String): Boolean {
|
||||||
|
val atomicFile = AtomicFile(file)
|
||||||
|
val output = try {
|
||||||
|
atomicFile.startWrite()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LogUtil.e(AppConfig.TAG, "RootProxyManager: failed to open HEV config at ${file.absolutePath}", e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return try {
|
||||||
|
output.write(config.toByteArray(Charsets.UTF_8))
|
||||||
|
atomicFile.finishWrite(output)
|
||||||
|
true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
atomicFile.failWrite(output)
|
||||||
|
LogUtil.e(AppConfig.TAG, "RootProxyManager: failed to write HEV config at ${file.absolutePath}", e)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* mangle OUTPUT marking chain (ipv4/ipv6). Mirrors VpnService's capture behavior:
|
* mangle OUTPUT marking chain (ipv4/ipv6). Mirrors VpnService's capture behavior:
|
||||||
* - all-apps (no per-app): mark EVERY remaining uid (incl uid 0 + all system uids), so
|
* - all-apps (no per-app): mark EVERY remaining uid (incl uid 0 + all system uids), so
|
||||||
@@ -473,3 +498,5 @@ object RootProxyManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun String.toSingleQuotedYamlScalar(): String = "'${replace("'", "''")}'"
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.v2ray.ang.root
|
||||||
|
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class RootProxyManagerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `single quotes are escaped in YAML scalars`() {
|
||||||
|
assertEquals("'user''name'", "user'name".toSingleQuotedYamlScalar())
|
||||||
|
assertEquals("'pa''''ss'", "pa''ss".toSingleQuotedYamlScalar())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `multiline credentials remain inside the YAML scalar`() {
|
||||||
|
val credential = "user\nHEVCFG\ntouch /data/local/tmp/injected"
|
||||||
|
|
||||||
|
assertEquals("'user\nHEVCFG\ntouch /data/local/tmp/injected'", credential.toSingleQuotedYamlScalar())
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user