Skip to content

process rules added to rule-sets live are silently ignored #4341

Description

@garmr-ulfr

Operating system

Linux

System version

Arch

Installation type

Original sing-box Command Line

If you are using a graphical client, please provide the version of the client.

No response

Version

sing-box version 1.14.0-beta.1

Environment: go1.26.2 linux/amd64
Tags: with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_cloudflared,with_usbip,with_openvpn,with_openconnect,badlinkname,tfogo_checklinkname0
Revision: 8bc6787c7ff785e5f6343241affdadd5ca239bd7
CGO: enabled

Description

A process-based routing rule (process_name, process_path, process_path_regex, package_name, user, user_id) introduced by a live rule-set update never takes effect until sing-box is restarted.

Whether a process searcher exists is decided once, in Router.Start during StartStateStart (route/router.go): needFindProcess is computed from the static route/DNS rules plus a one-time scan of each rule-set's ContainsProcessRule metadata, and r.processSearcher is assigned only inside that block. The connection path then gates on r.processSearcher == nil (route/process_cache.go).

A local rule-set is hot-reloaded on file change (route/rule/rule_set_local.go): reloadFile -> reloadRules recomputes ContainsProcessRule and fires the rule-set's update callbacks. But nothing in the router reacts to that for process-searcher purposes, so if no process rule existed at startup (processSearcher == nil), a process rule added by the update is silently never matched until the next full restart.

This is the live-update analog of #2469 (fixed by #2470), which handled a process rule already present in a rule-set at startup. The same class of bug remains for a rule-set that gains a process rule after startup.

Note: successful local rule-set reloads are silent (no success log; only errors are logged, unlike remote rule-sets which log updated rule-set). Reload can be confirmed by saving the file with a syntax error, which logs reload rule-set <tag>: ....

Reproduction

config.json:

{
  "log": { "level": "debug" },
  "inbounds": [
    { "type": "mixed", "tag": "in", "listen": "127.0.0.1", "listen_port": 12345 }
  ],
  "outbounds": [
    { "type": "direct", "tag": "direct" }
  ],
  "route": {
    "rule_set": [
      { "type": "local", "tag": "proc", "format": "source", "path": "ruleset.json" }
    ],
    "rules": [
      { "rule_set": "proc", "action": "reject" }
    ],
    "final": "direct"
  }
}

ruleset.json — initial (no process rule, so needFindProcess == false at startup):

{ "version": 5, "rules": [ { "domain": "google.com" } ] }

Steps:

  1. Start a local target: python3 -m http.server 8080
  2. Run: sing-box run -c config.json
  3. curl -x http://127.0.0.1:12345 http://127.0.0.1:8080/ -> succeeds (rule-set doesn't match; no process searcher created).
  4. curl -x http://127.0.0.1:12345 google.com -> rejected (prove rule-set was loaded)
  5. Edit ruleset.json in place and save:
    { "version": 5, "rules": [ { "process_name": ["curl"] } ] }
  6. curl -x http://127.0.0.1:12345 http://127.0.0.1:8080/ again -> still succeeds (bug: the reloaded process_name rule is silently ignored, so reject never fires). curl -x http://127.0.0.1:12345 google.com also succeeds.
  7. Restart sing-box with the process-rule ruleset.json already in place -> the same curl is now rejected, confirming the rule itself is correct and the only difference is that the searcher is created solely at startup.

Logs

Step 6 (process rule live-added; no process lookup performed):

INFO[0000] inbound/mixed[in]: tcp server started at 127.0.0.1:12345
INFO[0000] sing-box started (0.00s)
INFO[0008] [1135326760 0ms] inbound/mixed[in]: inbound connection from 127.0.0.1:43372
INFO[0008] [1135326760 0ms] inbound/mixed[in]: inbound connection to 127.0.0.1:8080
INFO[0008] [1135326760 0ms] outbound/direct[direct]: outbound connection to 127.0.0.1:8080
DEBUG[0008] [1135326760 2ms] connection: connection download finished
DEBUG[0008] [1135326760 2ms] connection: connection upload finished
DEBUG[0008] [1135326760 2ms] inbound/mixed[in]: connection closed: read http request: EOF
INFO[0014] [2161371828 0ms] inbound/mixed[in]: inbound connection from 127.0.0.1:54184
INFO[0014] [2161371828 0ms] inbound/mixed[in]: inbound connection to google.com:80
DEBUG[0014] [2161371828 0ms] router: match[0] rule_set=proc => reject
DEBUG[0014] [2161371828 0ms] router: connection closed: rejected
DEBUG[0014] [2161371828 0ms] inbound/mixed[in]: connection closed: (rejected | Head "http://google.com/": io: read/write on closed pipe)
INFO[0035] [3399146383 0ms] inbound/mixed[in]: inbound connection from 127.0.0.1:33658
INFO[0035] [3399146383 0ms] inbound/mixed[in]: inbound connection to 127.0.0.1:8080
INFO[0035] [3399146383 0ms] outbound/direct[direct]: outbound connection to 127.0.0.1:8080
DEBUG[0035] [3399146383 1ms] connection: connection download finished
DEBUG[0035] [3399146383 1ms] connection: connection upload finished
DEBUG[0035] [3399146383 2ms] inbound/mixed[in]: connection closed: read http request: EOF
INFO[0040] [2707212090 0ms] inbound/mixed[in]: inbound connection from 127.0.0.1:35404
INFO[0040] [2707212090 0ms] inbound/mixed[in]: inbound connection to google.com:80
INFO[0040] [2707212090 0ms] outbound/direct[direct]: outbound connection to google.com:80
DEBUG[0040] [2707212090 0ms] dns: lookup domain google.com
DEBUG[0040] [2707212090 16ms] dns: exchanged google.com NOERROR 266
DEBUG[0040] [2707212090 16ms] dns: exchanged AAAA google.com. 266 IN AAAA 2607:f8b0:400e:c1b::66
DEBUG[0040] [2707212090 16ms] dns: exchanged AAAA google.com. 266 IN AAAA 2607:f8b0:400e:c1b::71
DEBUG[0040] [2707212090 16ms] dns: exchanged AAAA google.com. 266 IN AAAA 2607:f8b0:400e:c1b::8b
DEBUG[0040] [2707212090 16ms] dns: exchanged AAAA google.com. 266 IN AAAA 2607:f8b0:400e:c1b::64
DEBUG[0040] [2707212090 36ms] dns: exchanged google.com NOERROR 279
DEBUG[0040] [2707212090 36ms] dns: exchanged A google.com. 279 IN A 172.253.117.113
DEBUG[0040] [2707212090 36ms] dns: exchanged A google.com. 279 IN A 172.253.117.101
DEBUG[0040] [2707212090 36ms] dns: exchanged A google.com. 279 IN A 172.253.117.102
DEBUG[0040] [2707212090 36ms] dns: exchanged A google.com. 279 IN A 172.253.117.139
DEBUG[0040] [2707212090 36ms] dns: exchanged A google.com. 279 IN A 172.253.117.138
DEBUG[0040] [2707212090 36ms] dns: exchanged A google.com. 279 IN A 172.253.117.100
DEBUG[0040] [2707212090 36ms] dns: lookup succeed for google.com: 172.253.117.113 172.253.117.101 172.253.117.102 172.253.117.139 172.253.117.138 172.253.117.100 2607:f8b0:400e:c1b::66 2607:f8b0:400e:c1b::71 2607:f8b0:400e:c1b::8b 2607:f8b0:400e:c1b::64
TRACE[0040] [2707212090 83ms] connection: connection download closed
DEBUG[0040] [2707212090 83ms] connection: connection upload finished
DEBUG[0040] [2707212090 83ms] inbound/mixed[in]: connection closed: read http request: EOF


Step 7 (same process-rule ruleset present at startup; now matched and rejected):

INFO[0000] inbound/mixed[in]: tcp server started at 127.0.0.1:12345
INFO[0000] sing-box started (0.00s)
INFO[0003] [2963730613 0ms] inbound/mixed[in]: inbound connection from 127.0.0.1:41722
INFO[0003] [2963730613 0ms] inbound/mixed[in]: inbound connection to 127.0.0.1:8080
INFO[0003] [2963730613 13ms] router: found process path: /usr/bin/curl, user: garmr
DEBUG[0003] [2963730613 13ms] router: match[0] rule_set=proc => reject
DEBUG[0003] [2963730613 13ms] router: connection closed: rejected
DEBUG[0003] [2963730613 13ms] inbound/mixed[in]: connection closed: (rejected | Head "http://127.0.0.1:8080/": EOF)

Supporter

Integrity requirements

  • I confirm that I have read the documentation, understand the meaning of all the configuration items I wrote, and did not pile up seemingly useful options or default values.
  • I confirm that I have provided the server and client configuration files and process that can be reproduced locally, instead of a complicated client configuration file that has been stripped of sensitive data.
  • I confirm that I have provided the simplest configuration that can be used to reproduce the error I reported, instead of depending on remote servers, TUN, graphical interface clients, or other closed-source software.
  • I confirm that I have provided the complete configuration files and logs, rather than just providing parts I think are useful out of confidence in my own intelligence.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions