Skip to content

Validate configuration window entries before writing them - #129

Merged
ericbsd merged 2 commits into
masterfrom
validate-config-entries
Oct 7, 2026
Merged

ericbsd merged 2 commits into
masterfrom
validate-config-entries

Conversation

@ericbsd

@ericbsd ericbsd commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

The configuration window wrote whatever was typed in the address, netmask, gateway, DNS and search fields to rc.conf and resolv.conf. sysrc does not escape values, and rc.conf is run by the shell as root at boot, so a value holding a quote, $(...) or a backtick ran as root. A newline in a DNS or search field also added lines to resolv.conf.

Add NetworkMgr/validate.py, which checks interface names, IPv4 and IPv6 addresses, netmasks, prefix lengths and search domains, and returns them normalized. Save now validates every entry first and shows an error dialog naming the bad field, writing nothing. The update code only uses the checked values. set_rc_conf() also refuses any name or value with characters outside a small safe set, as a last guard. An empty IPv4 gateway now removes defaultrouter instead of writing it empty.

Summary by Sourcery

Validate and safely apply all network configuration entries before persisting or activating them.

Bug Fixes:

  • Prevent invalid or shell-sensitive network settings from being written to rc.conf or resolv.conf.
  • Remove the IPv4 default route configuration when the gateway is cleared instead of writing an empty value.

Enhancements:

  • Validate and normalize interface names, IP addresses, netmasks, IPv6 prefixes, and search domains before applying configuration.
  • Keep the configuration window open and identify the invalid field when validation fails.
  • Add a final safety check for rc.conf variable names and values before invoking sysrc.
  • Support safe normalization of interface names used in rc.conf and apply validated values consistently to running and persistent configuration.

The configuration window wrote whatever was typed in the address,
netmask, gateway, DNS and search fields to rc.conf and resolv.conf.
sysrc does not escape values, and rc.conf is run by the shell as root
at boot, so a value holding a quote, $(...) or a backtick ran as root.
A newline in a DNS or search field also added lines to resolv.conf.

Add NetworkMgr/validate.py, which checks interface names, IPv4 and
IPv6 addresses, netmasks, prefix lengths and search domains, and
returns them normalized. Save now validates every entry first and
shows an error dialog naming the bad field, writing nothing. The
update code only uses the checked values. set_rc_conf() also refuses
any name or value with characters outside a small safe set, as a last
guard. An empty IPv4 gateway now removes defaultrouter instead of
writing it empty.
@ericbsd ericbsd self-assigned this Oct 6, 2026
@ericbsd
ericbsd requested review from a team as code owners October 6, 2026 21:56
@sourcery-ai

sourcery-ai Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Reviewer's Guide

The configuration window now validates and normalizes every applicable network field before saving, blocks invalid or unsafe values from reaching rc.conf or resolv.conf, and applies only the validated settings; blank IPv4 gateways now remove the persisted default route.

Sequence diagram for validated network configuration save

sequenceDiagram
    actor User
    participant Window as NetCardConfigWindow
    participant Validator as NetworkMgr.validate
    participant System as rc.conf and resolv.conf
    User->>Window: commit_pending_changes()
    Window->>Validator: validated_settings()
    alt invalid entry
        Validator-->>Window: ValueError
        Window->>Window: show_invalid_entry(message)
        Window-->>User: Error dialog, nothing written
    else valid entries
        Validator-->>Window: normalized settings
        Window->>System: set_rc_conf(name, value)
        Window->>System: write resolv.conf
        Window->>System: update_system_ipv6(settings)
    end
Loading

Flow diagram for safe configuration persistence

flowchart TD
    A[User selects Save] --> B["validated_settings()"]
    B --> C{All applicable fields valid?}
    C -- No --> D["show_invalid_entry(message)"]
    D --> E[Keep window open]
    C -- Yes --> F[Store normalized values in pending]
    F --> G["set_rc_conf(name, value)"]
    G --> H{Safe rc.conf name and value?}
    H -- No --> I[Refuse write]
    H -- Yes --> J[Write rc.conf and resolv.conf]
    J --> K{IPv4 gateway empty?}
    K -- Yes --> L["remove_rc_conf_var(defaultrouter)"]
    K -- No --> M[Persist defaultrouter]
Loading

File-Level Changes

Change Details Files
Add centralized validation and normalization for all configuration-window inputs before any system writes.
  • Validate interface names, IPv4/IPv6 addresses, netmasks, prefix lengths, and search domains with field-specific errors.
  • Normalize accepted addresses, netmasks, prefix lengths, and search-domain formatting.
  • Reject invalid input with an error dialog while keeping the window open and preventing writes.
  • Pass only the validated pending settings into the update path.
NetworkMgr/validate.py
NetworkMgr/configuration.py
Harden rc.conf writes against shell-injection characters with a final allowlist guard.
  • Restrict rc.conf variable names and values to safe character sets before invoking sysrc.
  • Refuse unsafe writes with ValueError as a defense-in-depth check.
NetworkMgr/configuration.py
Adjust gateway persistence and resolv.conf generation to use safe validated values.
  • Remove defaultrouter when the IPv4 gateway is blank instead of writing an empty value.
  • Generate resolv.conf entries from validated DNS and search-domain values.
NetworkMgr/configuration.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 5 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="NetworkMgr/configuration.py" line_range="624" />
<code_context>
-        inet = self.ip_input_address_entry.get_text()
-        netmask = self.ip_input_mask_entry.get_text()
-        defaultrouter = self.ip_input_gateway_entry.get_text()
+        settings = self.pending
+        nic = settings['nic']
         if self.method == 'Manual':
</code_context>
<issue_to_address>
**DHCP saves stop partway through**

When the IPv4 method is DHCP, `update_system` reaches `start_static_network` with `inet` and `netmask` unassigned, raising `UnboundLocalError` after the DHCP settings are written and the interface is restarted. IPv6 updates and cleanup are skipped, leaving the user with a partial save.

Keep the DHCP path from passing unassigned locals to `start_static_network`; use the appropriate DHCP operation instead.

Also at `NetworkMgr/configuration.py:627-628`.
</issue_to_address>

### Comment 2
<location path="NetworkMgr/configuration.py" line_range="42" />
<code_context>
+RC_CONF_SAFE_REGEX = re.compile(r'[A-Za-z0-9_.:%/ -]*')
+# An rc.conf variable name. A "." from a vlan name such as em0.10 is let
+# through, sysrc refuses it on its own.
+RC_CONF_NAME_REGEX = re.compile(r'[A-Za-z_][A-Za-z0-9_.]*')


</code_context>
<issue_to_address>
**Dotted interface settings are not saved**

When the selected interface name contains a dot and the user saves settings, `RC_CONF_NAME_REGEX` accepts the dotted variable name, but `sysrc` rejects it and `set_rc_conf` ignores the failure with `check=False`. The live setting is applied but is not persisted, so it is lost on reboot.

Ensure dotted interface settings are persisted successfully, or reject them before applying the live change.

Also at `NetworkMgr/validate.py:17`, `NetworkMgr/configuration.py:43`.
</issue_to_address>

### Comment 3
<location path="NetworkMgr/validate.py" line_range="118" />
<code_context>
+    error = ValueError(f'{field}: "{text}" is not a valid IPv6 address.')
+    address, _, scope = value.partition('%')
+    if scope or value.endswith('%'):
+        if not allow_scope or not INTERFACE_REGEX.fullmatch(scope):
+            raise error
+    try:
</code_context>
<issue_to_address>
**Scoped gateway routes fail**

When the IPv6 gateway scope does not identify the selected interface, `ipv6_address` checks only the scope syntax, then `update_system_ipv6` passes the scope through to `route add` and ignores command failure. The gateway is saved without a usable route, so the user sees an unusable IPv6 gateway.

Validate that the gateway scope identifies the selected interface before saving or applying the gateway.

Also at `NetworkMgr/configuration.py:593-594`.
</issue_to_address>

### Comment 4
<location path="NetworkMgr/validate.py" line_range="186-187" />
<code_context>
+    """
+    domains = text.split()
+    for domain in domains:
+        labels = domain.rstrip('.').split('.')
+        if len(domain) > 253 or not all(LABEL_REGEX.fullmatch(label) for label in labels):
+            raise ValueError(f'Search domains: "{domain}" is not a valid domain name.')
+    joined = ' '.join(domains)
</code_context>
<issue_to_address>
**Malformed search suffixes are accepted**

When a search-domain entry has multiple trailing dots, `search_domains` removes every trailing dot for label validation but returns the original token. An entry such as `example.com..` passes validation and is written to `resolv.conf` as a malformed suffix, so lookups using it fail or behave incorrectly.

Reject multiple trailing dots while allowing at most one optional root dot during validation.
</issue_to_address>

### Comment 5
<location path="NetworkMgr/validate.py" line_range="165" />
<code_context>
+        ValueError: when it is not a whole number from 1 to 128.
+    """
+    value = text.strip()
+    if not PREFIX_REGEX.fullmatch(value) or not 1 <= int(value) <= 128:
+        raise ValueError(f'Prefix Length: "{text}" must be a number from 1 to 128.')
+    return str(int(value))
</code_context>
<issue_to_address>
**Valid IPv6 /0 is rejected**

When a user configures a manual IPv6 address with prefix length 0, `ipv6_prefixlen` rejects 0 even though `start_static_ipv6_network` can pass that valid prefix length to `ifconfig`, so Save refuses the configuration.

Accept prefix lengths from 0 through 128 and update the validation message accordingly.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 5 findings to address first, and if the validation or normalization is wrong, the change can write an invalid interface, route, resolver, or shell-backed rc.conf value and immediately cause a network outage or security exposure. Reverting the code will not restore already-written rc.conf, resolv.conf, or running-system state, although the configuration can be repaired and reapplied.

Blocking findings: NetworkMgr/configuration.py:624, NetworkMgr/configuration.py:42, NetworkMgr/validate.py:118, NetworkMgr/validate.py:187, NetworkMgr/validate.py:165


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread NetworkMgr/configuration.py Resolved
Comment thread NetworkMgr/configuration.py Resolved
Comment thread NetworkMgr/validate.py Resolved
Comment thread NetworkMgr/validate.py Resolved
Comment thread NetworkMgr/validate.py Resolved
Switching an interface to DHCP raised UnboundLocalError partway
through the save, because the interim start_static_network call used
locals that only the manual path set. The greyed out address entries
are now checked in DHCP mode too, and the call is skipped when they
hold no valid address.

rc.subr reads the settings of em0.10 from ifconfig_em0_10, and sysrc
refuses a name with a dot, so settings for dotted interfaces were
silently not saved. Add validate.rc_conf_interface(), which maps
".-/+" to "_" like get_if_var in network.subr, and use it for every
ifconfig_* variable written or read.

An IPv6 gateway zone must now name the interface being configured.
Search domains with more than one trailing dot are refused, and so is
a zero IPv4 netmask.
@ericbsd
ericbsd merged commit bc51e9f into master Oct 7, 2026
1 check passed
@ericbsd
ericbsd deleted the validate-config-entries branch October 7, 2026 22:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done
Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant