wgengine/router/osrouter: sanitize interfaceV6UsableForTun path with os.OpenInRoot (#20505)
interfaceV6UsableForTun interpolates the interface name into a /proc path. A plain filepath.Join + os.Open only cleans the path, so a tunname with ".." (or a symlinked component) could read outside /proc/sys/net/ipv6/conf. Open under that fixed directory with os.OpenInRoot, which rejects any path escaping the root (openat-based, so also TOCTOU-resistant), still using filepath.Join to build the relative name. See https://go.dev/blog/osroot. Updates #20447 Signed-off-by: Brendan Creane <bcreane@gmail.com>
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
@@ -963,14 +964,21 @@ func interfaceV6UsableForTun(tunname string) bool {
|
|||||||
if tunname == "" {
|
if tunname == "" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
bs, err := os.ReadFile(filepath.Join("/proc/sys/net/ipv6/conf", tunname, "disable_ipv6"))
|
// Open under conf/ with os.OpenInRoot so a "../" or symlink in tunname can't
|
||||||
|
// escape the directory.
|
||||||
|
f, err := os.OpenInRoot("/proc/sys/net/ipv6/conf", filepath.Join(tunname, "disable_ipv6"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// A missing directory/knob means IPv6 isn't up on the interface, so
|
// A missing directory/knob means IPv6 isn't up on the interface, so it's
|
||||||
// it's unavailable. Any other error (e.g. EACCES) means the knob
|
// unavailable. Any other error (e.g. EACCES, or tunname escaping the
|
||||||
// exists but we couldn't read it; assume IPv6 is usable rather than
|
// root) means we couldn't read the knob; assume IPv6 is usable rather
|
||||||
// skipping it on a transient error.
|
// than skipping it on a transient or defensive error.
|
||||||
return !os.IsNotExist(err)
|
return !os.IsNotExist(err)
|
||||||
}
|
}
|
||||||
|
defer f.Close()
|
||||||
|
bs, err := io.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
return true // couldn't read; assume usable
|
||||||
|
}
|
||||||
disabled, err := strconv.ParseBool(strings.TrimSpace(string(bs)))
|
disabled, err := strconv.ParseBool(strings.TrimSpace(string(bs)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return true // unparseable; assume usable
|
return true // unparseable; assume usable
|
||||||
|
|||||||
Reference in New Issue
Block a user