fix: harden target recovery metadata safety
This commit is contained in:
+64
-5
@@ -26,10 +26,11 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
proxySchema = "org.gnome.system.proxy"
|
||||
httpSchema = proxySchema + ".http"
|
||||
httpsSchema = proxySchema + ".https"
|
||||
socksSchema = proxySchema + ".socks"
|
||||
gnomeRecoveryVersion = 1
|
||||
proxySchema = "org.gnome.system.proxy"
|
||||
httpSchema = proxySchema + ".http"
|
||||
httpsSchema = proxySchema + ".https"
|
||||
socksSchema = proxySchema + ".socks"
|
||||
)
|
||||
|
||||
var requiredSchemas = []string{proxySchema, httpSchema, httpsSchema, socksSchema}
|
||||
@@ -226,6 +227,7 @@ type settings struct {
|
||||
}
|
||||
|
||||
type recovery struct {
|
||||
Version int `json:"version"`
|
||||
Original settings `json:"original"`
|
||||
LastFingerprint string `json:"lastFingerprint"`
|
||||
}
|
||||
@@ -421,6 +423,7 @@ func (a *Adapter) enable(ctx context.Context, active profile.Profile) error {
|
||||
return fmt.Errorf("read GNOME recovery metadata: %w", err)
|
||||
}
|
||||
if !managed {
|
||||
metadata.Version = gnomeRecoveryVersion
|
||||
original, err := a.readSettings(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("snapshot original GNOME settings: %w", err)
|
||||
@@ -480,13 +483,59 @@ func (a *Adapter) readRecovery() (recovery, bool, error) {
|
||||
if err != nil {
|
||||
return recovery{}, false, err
|
||||
}
|
||||
var fields map[string]json.RawMessage
|
||||
if err := json.Unmarshal(data, &fields); err != nil {
|
||||
return recovery{}, true, err
|
||||
}
|
||||
if fields == nil {
|
||||
return recovery{}, true, errors.New("GNOME recovery metadata must be a JSON object")
|
||||
}
|
||||
for _, name := range []string{"version", "original", "lastFingerprint"} {
|
||||
if _, present := fields[name]; !present {
|
||||
return recovery{}, true, fmt.Errorf("GNOME recovery metadata is missing required field %q", name)
|
||||
}
|
||||
}
|
||||
var originalFields map[string]json.RawMessage
|
||||
if err := json.Unmarshal(fields["original"], &originalFields); err != nil || originalFields == nil {
|
||||
return recovery{}, true, errors.New("GNOME recovery metadata original settings must be a JSON object")
|
||||
}
|
||||
for _, name := range []string{"mode", "autoConfigUrl", "ignoreHosts", "httpHost", "httpPort", "httpUseAuthentication", "httpAuthenticationUser", "httpAuthenticationPassword", "httpsHost", "httpsPort", "socksHost", "socksPort"} {
|
||||
if _, present := originalFields[name]; !present {
|
||||
return recovery{}, true, fmt.Errorf("GNOME recovery metadata original settings are missing required field %q", name)
|
||||
}
|
||||
}
|
||||
var value recovery
|
||||
if err := json.Unmarshal(data, &value); err != nil {
|
||||
return recovery{}, true, err
|
||||
}
|
||||
if err := validateRecovery(value); err != nil {
|
||||
return recovery{}, true, err
|
||||
}
|
||||
return value, true, nil
|
||||
}
|
||||
|
||||
func validateRecovery(value recovery) error {
|
||||
if value.Version != gnomeRecoveryVersion {
|
||||
return fmt.Errorf("unsupported GNOME recovery metadata version %d", value.Version)
|
||||
}
|
||||
decoded, err := hex.DecodeString(value.LastFingerprint)
|
||||
if err != nil || len(decoded) != sha256.Size {
|
||||
return errors.New("GNOME recovery metadata has an invalid fingerprint")
|
||||
}
|
||||
if value.Original.Mode != "none" && value.Original.Mode != "manual" && value.Original.Mode != "auto" {
|
||||
return errors.New("GNOME recovery metadata has an invalid original proxy mode")
|
||||
}
|
||||
if value.Original.IgnoreHosts == nil {
|
||||
return errors.New("GNOME recovery metadata has an incomplete original ignore-hosts snapshot")
|
||||
}
|
||||
for _, port := range []int{value.Original.HTTPPort, value.Original.HTTPSPort, value.Original.SocksPort} {
|
||||
if port < 0 || port > 65535 {
|
||||
return errors.New("GNOME recovery metadata has an invalid original proxy port")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Adapter) command(args ...string) Command {
|
||||
return Command{Name: a.configuration.CommandName, Args: append([]string(nil), args...), UID: a.configuration.UID, GID: a.configuration.GID, Env: map[string]string{
|
||||
"HOME": a.configuration.HomeDir, "USER": a.configuration.Username, "LOGNAME": a.configuration.Username,
|
||||
@@ -511,6 +560,13 @@ func (a *Adapter) set(ctx context.Context, schema, key, value string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Adapter) setSensitiveString(ctx context.Context, schema, key, value string) error {
|
||||
if _, err := a.run(ctx, "set", schema, key, value); err != nil {
|
||||
return safeRedaction("set GNOME authentication password failed; sensitive argument and runner error suppressed", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Adapter) readSettings(ctx context.Context) (settings, error) {
|
||||
var result settings
|
||||
var err error
|
||||
@@ -615,7 +671,7 @@ func (a *Adapter) writeSettings(ctx context.Context, value settings) error {
|
||||
{proxySchema, "autoconfig-url", quote(value.AutoConfigURL)},
|
||||
{proxySchema, "ignore-hosts", quoteStrings(value.IgnoreHosts)},
|
||||
{httpSchema, "host", quote(value.HTTPHost)}, {httpSchema, "port", strconv.Itoa(value.HTTPPort)},
|
||||
{httpSchema, "use-authentication", strconv.FormatBool(value.HTTPUseAuth)}, {httpSchema, "authentication-user", quote(value.HTTPAuthUser)}, {httpSchema, "authentication-password", quote(value.HTTPAuthPassword)},
|
||||
{httpSchema, "use-authentication", strconv.FormatBool(value.HTTPUseAuth)}, {httpSchema, "authentication-user", quote(value.HTTPAuthUser)},
|
||||
{httpsSchema, "host", quote(value.HTTPSHost)}, {httpsSchema, "port", strconv.Itoa(value.HTTPSPort)},
|
||||
{socksSchema, "host", quote(value.SocksHost)}, {socksSchema, "port", strconv.Itoa(value.SocksPort)},
|
||||
}
|
||||
@@ -624,6 +680,9 @@ func (a *Adapter) writeSettings(ctx context.Context, value settings) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := a.setSensitiveString(ctx, httpSchema, "authentication-password", quote(value.HTTPAuthPassword)); err != nil {
|
||||
return err
|
||||
}
|
||||
return a.set(ctx, proxySchema, "mode", quote(value.Mode))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user