mirror of
https://github.com/okhsunrog/vpnhide.git
synced 2026-04-28 06:31:27 +00:00
fix(filter): catch tunnels renamed to if<N> (issue #86)
Add a single TOML rule `prefix = "if", suffix = "digits"` to the shared matcher. Renames using the kernel's default anonymous-netdev naming (`ip link set tun0 name if33`) — the exact attack from issue #86 — now get hidden by every component (kmod, zygisk, lsposed, lsposed-native). The shape is intentionally narrow: `if` + 1+ ASCII digits only. `ifb<N>` (intermediate-functional-block traffic shaping) has a letter after `if` and is not matched.
This commit is contained in:
parent
15d806a885
commit
54242b1140
8 changed files with 89 additions and 8 deletions
|
|
@ -0,0 +1,9 @@
|
|||
_2026-04-26_
|
||||
|
||||
## English
|
||||
|
||||
Catch tunnels renamed to the kernel default `if<N>` pattern (issue #86) — e.g. `tun0` renamed to `if33` is no longer visible to target apps.
|
||||
|
||||
## Русский
|
||||
|
||||
Прячем туннели, переименованные в дефолтный для ядра паттерн `if<N>` (issue #86) — например `tun0` с именем `if33` теперь скрыт от целевых приложений.
|
||||
|
|
@ -63,6 +63,13 @@ note = "GRE tunnels"
|
|||
match = { contains = "vpn" }
|
||||
note = "catch-all for renamed clients (myvpn0, vpn-client, xvpn1, ...)"
|
||||
|
||||
[[vpn]]
|
||||
match = { prefix = "if", suffix = "digits" }
|
||||
note = """Anonymous netdev / renamed tunnel using the kernel's default \
|
||||
naming pattern (e.g. `ip link set tun0 name if33` from issue #86). \
|
||||
Does NOT match `ifb<N>` — those are kernel intermediate-functional-block \
|
||||
traffic-shaping ifaces (different shape: `if` + letter, not + digit)."""
|
||||
|
||||
|
||||
# ── Test vectors ──────────────────────────────────────────────────────
|
||||
# Array of {name, is_vpn} fixtures. Codegen renders these into per-
|
||||
|
|
@ -198,12 +205,40 @@ is_vpn = false
|
|||
name = "rndis0"
|
||||
is_vpn = false
|
||||
|
||||
# The renamed-tun trick from issue #86 — name-only matching cannot
|
||||
# catch this; here just confirms the matcher does not over-match on a
|
||||
# generic prefix.
|
||||
# The renamed-tun trick from issue #86 — caught by the
|
||||
# `if` + digits rule above.
|
||||
|
||||
[[test]]
|
||||
name = "if33"
|
||||
is_vpn = true
|
||||
|
||||
[[test]]
|
||||
name = "if0"
|
||||
is_vpn = true
|
||||
|
||||
[[test]]
|
||||
name = "if99"
|
||||
is_vpn = true
|
||||
|
||||
# `ifb<N>` is the kernel's intermediate-functional-block (traffic
|
||||
# shaping). Different shape (`if` + letter) — must NOT match.
|
||||
|
||||
[[test]]
|
||||
name = "ifb0"
|
||||
is_vpn = false
|
||||
|
||||
[[test]]
|
||||
name = "ifb1"
|
||||
is_vpn = false
|
||||
|
||||
# `if` alone or with non-digit suffix — must NOT match.
|
||||
|
||||
[[test]]
|
||||
name = "if"
|
||||
is_vpn = false
|
||||
|
||||
[[test]]
|
||||
name = "if_inet6"
|
||||
is_vpn = false
|
||||
|
||||
# Edge cases
|
||||
|
|
|
|||
|
|
@ -131,6 +131,9 @@ static inline bool vpnhide_iface_is_vpn(const char *name)
|
|||
/* catch-all for renamed clients (myvpn0, vpn-client, xvpn1, ...) */
|
||||
if (vpnhide_iface_contains_ci(name, "vpn"))
|
||||
return true;
|
||||
/* Anonymous netdev / renamed tunnel using the kernel's default naming pattern (e.g. `ip link set tun0 name if33` from issue #86). Does NOT match `ifb<N>` — those are kernel intermediate-functional-block traffic-shaping ifaces (different shape: `if` + letter, not + digit). */
|
||||
if (vpnhide_iface_starts_with_then_digits_ci(name, "if"))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,13 @@ int main(void)
|
|||
check("dummy0", false);
|
||||
check("bnep0", false);
|
||||
check("rndis0", false);
|
||||
check("if33", false);
|
||||
check("if33", true);
|
||||
check("if0", true);
|
||||
check("if99", true);
|
||||
check("ifb0", false);
|
||||
check("ifb1", false);
|
||||
check("if", false);
|
||||
check("if_inet6", false);
|
||||
check("", false);
|
||||
check("tunl", true);
|
||||
check("atun0", false);
|
||||
|
|
@ -65,6 +71,6 @@ int main(void)
|
|||
fprintf(stderr, "%d test(s) failed\n", failures);
|
||||
return 1;
|
||||
}
|
||||
printf("OK: 36 vectors passed\n");
|
||||
printf("OK: 42 vectors passed\n");
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ internal object IfaceLists {
|
|||
if (n.startsWith("gre")) return true
|
||||
// catch-all for renamed clients (myvpn0, vpn-client, xvpn1, ...)
|
||||
if (n.contains("vpn")) return true
|
||||
// Anonymous netdev / renamed tunnel using the kernel's default naming pattern (e.g. `ip link set tun0 name if33` from issue #86). Does NOT match `ifb<N>` — those are kernel intermediate-functional-block traffic-shaping ifaces (different shape: `if` + letter, not + digit).
|
||||
if (n.startsWith("if") && n.length > 2 && n.substring(2).all { it.isDigit() }) return true
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,13 @@ class IfaceListsGeneratedTest {
|
|||
assertEquals("dummy0", false, IfaceLists.isVpnIface("dummy0"))
|
||||
assertEquals("bnep0", false, IfaceLists.isVpnIface("bnep0"))
|
||||
assertEquals("rndis0", false, IfaceLists.isVpnIface("rndis0"))
|
||||
assertEquals("if33", false, IfaceLists.isVpnIface("if33"))
|
||||
assertEquals("if33", true, IfaceLists.isVpnIface("if33"))
|
||||
assertEquals("if0", true, IfaceLists.isVpnIface("if0"))
|
||||
assertEquals("if99", true, IfaceLists.isVpnIface("if99"))
|
||||
assertEquals("ifb0", false, IfaceLists.isVpnIface("ifb0"))
|
||||
assertEquals("ifb1", false, IfaceLists.isVpnIface("ifb1"))
|
||||
assertEquals("if", false, IfaceLists.isVpnIface("if"))
|
||||
assertEquals("if_inet6", false, IfaceLists.isVpnIface("if_inet6"))
|
||||
assertEquals("", false, IfaceLists.isVpnIface(""))
|
||||
assertEquals("tunl", true, IfaceLists.isVpnIface("tunl"))
|
||||
assertEquals("atun0", false, IfaceLists.isVpnIface("atun0"))
|
||||
|
|
|
|||
|
|
@ -107,6 +107,10 @@ pub fn matches_vpn(name: &[u8]) -> bool {
|
|||
if contains_ci(name, b"vpn") {
|
||||
return true;
|
||||
}
|
||||
// Anonymous netdev / renamed tunnel using the kernel's default naming pattern (e.g. `ip link set tun0 name if33` from issue #86). Does NOT match `ifb<N>` — those are kernel intermediate-functional-block traffic-shaping ifaces (different shape: `if` + letter, not + digit).
|
||||
if starts_with_then_digits_ci(name, b"if") {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
|
|
@ -148,7 +152,13 @@ mod tests {
|
|||
assert_eq!(matches_vpn(b"dummy0"), false, "matches_vpn('dummy0')");
|
||||
assert_eq!(matches_vpn(b"bnep0"), false, "matches_vpn('bnep0')");
|
||||
assert_eq!(matches_vpn(b"rndis0"), false, "matches_vpn('rndis0')");
|
||||
assert_eq!(matches_vpn(b"if33"), false, "matches_vpn('if33')");
|
||||
assert_eq!(matches_vpn(b"if33"), true, "matches_vpn('if33')");
|
||||
assert_eq!(matches_vpn(b"if0"), true, "matches_vpn('if0')");
|
||||
assert_eq!(matches_vpn(b"if99"), true, "matches_vpn('if99')");
|
||||
assert_eq!(matches_vpn(b"ifb0"), false, "matches_vpn('ifb0')");
|
||||
assert_eq!(matches_vpn(b"ifb1"), false, "matches_vpn('ifb1')");
|
||||
assert_eq!(matches_vpn(b"if"), false, "matches_vpn('if')");
|
||||
assert_eq!(matches_vpn(b"if_inet6"), false, "matches_vpn('if_inet6')");
|
||||
assert_eq!(matches_vpn(b""), false, "matches_vpn('')");
|
||||
assert_eq!(matches_vpn(b"tunl"), true, "matches_vpn('tunl')");
|
||||
assert_eq!(matches_vpn(b"atun0"), false, "matches_vpn('atun0')");
|
||||
|
|
|
|||
|
|
@ -107,6 +107,10 @@ pub fn matches_vpn(name: &[u8]) -> bool {
|
|||
if contains_ci(name, b"vpn") {
|
||||
return true;
|
||||
}
|
||||
// Anonymous netdev / renamed tunnel using the kernel's default naming pattern (e.g. `ip link set tun0 name if33` from issue #86). Does NOT match `ifb<N>` — those are kernel intermediate-functional-block traffic-shaping ifaces (different shape: `if` + letter, not + digit).
|
||||
if starts_with_then_digits_ci(name, b"if") {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
|
|
@ -148,7 +152,13 @@ mod tests {
|
|||
assert_eq!(matches_vpn(b"dummy0"), false, "matches_vpn('dummy0')");
|
||||
assert_eq!(matches_vpn(b"bnep0"), false, "matches_vpn('bnep0')");
|
||||
assert_eq!(matches_vpn(b"rndis0"), false, "matches_vpn('rndis0')");
|
||||
assert_eq!(matches_vpn(b"if33"), false, "matches_vpn('if33')");
|
||||
assert_eq!(matches_vpn(b"if33"), true, "matches_vpn('if33')");
|
||||
assert_eq!(matches_vpn(b"if0"), true, "matches_vpn('if0')");
|
||||
assert_eq!(matches_vpn(b"if99"), true, "matches_vpn('if99')");
|
||||
assert_eq!(matches_vpn(b"ifb0"), false, "matches_vpn('ifb0')");
|
||||
assert_eq!(matches_vpn(b"ifb1"), false, "matches_vpn('ifb1')");
|
||||
assert_eq!(matches_vpn(b"if"), false, "matches_vpn('if')");
|
||||
assert_eq!(matches_vpn(b"if_inet6"), false, "matches_vpn('if_inet6')");
|
||||
assert_eq!(matches_vpn(b""), false, "matches_vpn('')");
|
||||
assert_eq!(matches_vpn(b"tunl"), true, "matches_vpn('tunl')");
|
||||
assert_eq!(matches_vpn(b"atun0"), false, "matches_vpn('atun0')");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue