Skip to content

Conversation

@yingziwu
Copy link

@yingziwu yingziwu commented May 9, 2025

对 smartdns.service 进行加固,具体内容如下:

使用非特权用户运行服务

DynamicUser=yes
RuntimeDirectory=smartdns     #创建可写的 `/run/smartdns` 目录,用于PIDFile
LogsDirectory=smartdns        #创建可写的 `/var/log/smartdns` 目录,用于日志
CacheDirectory=smartdns       #创建可写的 `/var/cache/smartdns` 目录,用于持久化缓存

因为有了 RuntimeDirectory,这里不再需要 @RUNSTATEDIR@

PIDFile=/run/smartdns/smartdns.pid

因为 /etc/smartdns/smartdns.conf 默认为 root 0640 权限,使用 LoadCredential 使非特权用户可以读取配置文件。

LoadCredential=smartdns.conf:@SYSCONFDIR@/smartdns/smartdns.conf

授予运行服务所需特权:

  • CAP_NET_BIND_SERVICE 绑定特权端口(1024以下)
  • CAP_NET_ADMIN 管理防火墙设置,ipsetnftset 选项所必须
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_NET_ADMIN
AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_NET_ADMIN

一大堆加固选项,具体说明可以读 systemd 文档

DevicePolicy=closed
LockPersonality=true
MemoryAccounting=true
MemoryDenyWriteExecute=true
NoNewPrivileges=true
PrivateDevices=true
PrivateTmp=true
ProcSubset=pid
ProtectClock=true
ProtectControlGroups=true
ProtectHome=true
ProtectHostname=true
ProtectKernelLogs=true
ProtectKernelModules=true
ProtectKernelTunables=true
ProtectProc=invisible
ProtectSystem=strict
RemoveIPC=true
RestrictNamespaces=true
RestrictRealtime=true
RestrictSUIDSGID=true

@PikuZheng
Copy link
Contributor

这些选项是否会导致ipv6 ping或nftables的ipset不可用?

@yingziwu
Copy link
Author

yingziwu commented May 9, 2025

这些选项是否会导致ipv6 ping或nftables的ipset不可用?

不会,已经测试过了。

ip 测速功能正常

[2025-05-09 22:44:15,370] 127.0.0.1 query example.com, type 1, time 0ms, speed: 278.9ms, group default, result 23.215.0.136, 23.215.0.138
[2025-05-09 22:44:19,088] 127.0.0.1 query example.com, type 28, time 0ms, speed: 273.4ms, group default, result 2600:1406:3a00:21::173e:2e65, 2600:1406:3a00:21::173e:2e66, 2600:1408:ec00:36::1736:7f24, 2600:1408:ec00:36::1736:7f31

ipset、nftset 都可以正常添加

选项相关配置文件

nftset /example.com/#4:ip#smartdns#public_set
ipset /example.com/smartdns

查询后,可以看到相关IP已被添加至 nft set、ipset

# nft list set ip smartdns public_set
table ip smartdns {
	set public_set {
		type ipv4_addr
		flags interval
		auto-merge
		elements = { 23.215.0.136, 23.215.0.138 }
	}
}

# ipset list smartdns
Name: smartdns
Type: hash:net
Revision: 7
Header: family inet hashsize 1024 maxelem 65536 bucketsize 12 initval 0xe8b8173d
Size in memory: 552
References: 0
Number of entries: 2
Members:
23.215.0.136
23.215.0.138

@yingziwu
Copy link
Author

yingziwu commented May 9, 2025

简单解释一下,那一大堆加固选项的含义吧。

  • DevicePolicy=closed 除 /dev/null, /dev/zero, /dev/full, /dev/random, /dev/urandom 外,阻止程序对物理设备的访问。
  • LockPersonality=true 阻止程序修改 personality
  • MemoryAccounting=true 启用内存用量统计
  • MemoryDenyWriteExecute=true 禁止创建或修改 memory mappings 为 executable
  • NoNewPrivileges=true 阻止服务进程及其子进程通过 execve() 获取新的特权
  • PrivateDevices=true 隔离 /dev/
  • PrivateTmp=true 隔离 /tmp/
  • ProtectProc=invisible 隔离 /proc/,程序无法看到其它用户的进程
  • ProcSubset=pid 只能查看与自己进程相关的 /proc/ 信息
  • ProtectClock=true 禁止修改物理时钟及系统时钟
  • ProtectControlGroups=true 设置 /sys/fs/cgroup/ 为只读(read-only)
  • ProtectHome=true 禁止访问 /home/, /root, /run/user
  • ProtectHostname=true 禁止程序修改 hostname 或监听 hostname 修改
  • ProtectKernelLogs=true 禁止程序访问 Kernel log ring buffer
  • ProtectKernelModules=true 禁止加载内核模块
  • ProtectKernelTunables=true 设置 /proc/sys/, /sys/, /proc/sysrq-trigger, /proc/latency_stats, /proc/acpi, /proc/timer_stats, /proc/fs, /proc/irq 为只读(read-only)。同时禁止访问 /proc/kallsyms/proc/kcore
  • ProtectSystem=strict 除 /dev/, /proc/, /sys/ 外(这几个由 PrivateDevices=, ProtectKernelTunables=, ProtectControlGroups= 控制),从 / 开始整个文件层级都将被挂载为只读。
  • RemoveIPC=true 当服务停止时,它所拥有的 System V 及 POSIX IPC objects 将会被移除
  • RestrictNamespaces=true 禁止进程访问 Linux namespace
  • RestrictRealtime=true 禁止进程启用实时任务调度策略(比如说:SCHED_FIFO, SCHED_RR, SCHED_DEADLINE),更多信息可参见 sched
  • RestrictSUIDSGID=true 禁止程序向文件或目录设置 set-user-ID (SUID) 或 set-group-ID (SGID)

参考资料:

@yingziwu
Copy link
Author

yingziwu commented May 9, 2025

systemd 中与网络服务关系比较大的 RestrictAddressFamilies=PrivateNetwork= 都没有使用,实际测试中也没有发现有这方面的问题。

ipset、nftset 已经通过 CAP_NET_ADMIN 权限解决,另外测试时发现移除 CAP_NET_ADMIN 权限,smartdns 添加 ipset、nftset 失败时日志中并没有添加失败的日志。

[2025-05-09 23:36:19,808][DEBUG][     dns_client.c:3366] new session
[2025-05-09 23:36:19,808][DEBUG][     dns_client.c:3251] peer CN: ibksturm.synology.me
[2025-05-09 23:36:19,808][DEBUG][     dns_client.c:3297] cert SPKI pin(sha256): 41:F7:EE:74:00:70:3F:CB:AD:CC:D5:6F:C4:CD:BB:BC:DE:2C:18:82:3C:96:C0:73:3C:59:18:48:04:EF:F7:B2
[2025-05-09 23:36:19,808][DEBUG][     dns_server.c:3148] from 23.215.0.138:443: seq=1 time=3104, lasttime=2765 id=0
[2025-05-09 23:36:19,808][DEBUG][      fast_ping.c:679 ] ping 23.215.0.138:443 end, id 125
[2025-05-09 23:36:19,811][DEBUG][     dns_server.c:3148] from 23.215.0.136:443: seq=1 time=3136, lasttime=2765 id=0
[2025-05-09 23:36:19,811][DEBUG][      fast_ping.c:679 ] ping 23.215.0.136:443 end, id 122
[2025-05-09 23:36:19,813][DEBUG][     dns_server.c:3148] from [2600:1408:ec00:0036:0000:0000:1736:7f31]:443: seq=1 time=3146, lasttime=2749 id=0
[2025-05-09 23:36:19,813][DEBUG][      fast_ping.c:679 ] ping [2600:1408:ec00:0036:0000:0000:1736:7f31]:443 end, id 133
[2025-05-09 23:36:19,824][DEBUG][     dns_server.c:3148] from 23.192.228.80:443: seq=1 time=3267, lasttime=2765 id=0
[2025-05-09 23:36:19,824][DEBUG][      fast_ping.c:679 ] ping 23.192.228.80:443 end, id 123
[2025-05-09 23:36:19,824][DEBUG][     dns_server.c:3148] from 23.192.228.84:443: seq=1 time=3264, lasttime=2765 id=0
[2025-05-09 23:36:19,824][DEBUG][      fast_ping.c:679 ] ping 23.192.228.84:443 end, id 126
[2025-05-09 23:36:19,837][DEBUG][     dns_server.c:3148] from [2600:1406:3a00:0021:0000:0000:173e:2e65]:443: seq=1 time=3388, lasttime=2749 id=0
[2025-05-09 23:36:19,837][DEBUG][      fast_ping.c:679 ] ping [2600:1406:3a00:0021:0000:0000:173e:2e65]:443 end, id 132
[2025-05-09 23:36:19,844][DEBUG][     dns_server.c:3148] from 96.7.128.175:443: seq=1 time=3461, lasttime=2765 id=0
[2025-05-09 23:36:19,844][DEBUG][      fast_ping.c:679 ] ping 96.7.128.175:443 end, id 124
[2025-05-09 23:36:19,848][DEBUG][     dns_server.c:3148] from [2600:1406:bc00:0053:0000:0000:b81e:94c8]:443: seq=1 time=3504, lasttime=2749 id=0
[2025-05-09 23:36:19,849][DEBUG][      fast_ping.c:679 ] ping [2600:1406:bc00:0053:0000:0000:b81e:94c8]:443 end, id 131
[2025-05-09 23:36:19,852][DEBUG][     dns_server.c:3148] from 96.7.128.198:443: seq=1 time=3536, lasttime=2765 id=0
[2025-05-09 23:36:19,852][DEBUG][      fast_ping.c:679 ] ping 96.7.128.198:443 end, id 127
[2025-05-09 23:36:19,854][DEBUG][     dns_client.c:2939] recv tcp packet from 2001:067c:0930:0000:0000:0000:0000:0001, len = 524
[2025-05-09 23:36:19,854][DEBUG][     dns_client.c:1868] qdcount = 1, ancount = 6, nscount = 0, nrcount = 0, len = 136, id = 11463, tc = 0, rd = 1, ra = 1, rcode = 0, payloadsize = 512
[2025-05-09 23:36:19,854][DEBUG][     dns_client.c:1880] domain: example.com qtype: 1  qclass: 1
[2025-05-09 23:36:19,854][DEBUG][     dns_server.c:3148] from [2600:1406:bc00:0053:0000:0000:b81e:94ce]:443: seq=1 time=3562, lasttime=2749 id=0
[2025-05-09 23:36:19,854][DEBUG][      fast_ping.c:679 ] ping [2600:1406:bc00:0053:0000:0000:b81e:94ce]:443 end, id 130
[2025-05-09 23:36:19,884][DEBUG][     dns_server.c:3148] from [2600:1406:3a00:0021:0000:0000:173e:2e66]:443: seq=1 time=3857, lasttime=2749 id=0
[2025-05-09 23:36:19,884][DEBUG][     dns_server.c:2337] reply example.com qtype: 28, rcode: 0, reply: 1
[2025-05-09 23:36:19,884][DEBUG][     dns_server.c:1197] result: example.com, rtt: 274.9 ms, 2600:1408:ec00:0036:0000:0000:1736:7f24
[2025-05-09 23:36:19,884][ INFO][     dns_server.c:969 ] result: example.com, id: 0, index: 1, rtt: 274.9 ms, 2600:1408:ec00:0036:0000:0000:1736:7f24
[2025-05-09 23:36:19,884][DEBUG][     dns_server.c:1667] cache example.com qtype: 28 ttl: 60
[2025-05-09 23:36:19,884][DEBUG][     dns_server.c:2027] IPSET-MATCH: domain: example.com, ipset: smartdns, IP: 2600:1408:ec00:0036:0000:0000:1736:7f24
[2025-05-09 23:36:19,884][DEBUG][     dns_server.c:4565] dualstack result: domain: example.com, ip: 2600:1408:ec00:0036:0000:0000:1736:7f24, type: 28, ping: 2749, rcode: 0
[2025-05-09 23:36:19,884][DEBUG][     dns_server.c:2337] reply example.com qtype: 1, rcode: 0, reply: 1
[2025-05-09 23:36:19,884][DEBUG][     dns_server.c:1190] result: example.com, rtt: 276.5 ms, 23.192.228.84
[2025-05-09 23:36:19,884][ INFO][     dns_server.c:965 ] result: example.com, id: 0, index: 1, rtt: 326.4 ms, 23.192.228.84
[2025-05-09 23:36:19,884][DEBUG][     dns_server.c:1667] cache example.com qtype: 1 ttl: 60
[2025-05-09 23:36:19,884][DEBUG][     dns_server.c:2023] IPSET-MATCH: domain: example.com, ipset: smartdns, IP: 23.192.228.84
[2025-05-09 23:36:19,884][DEBUG][     dns_server.c:2039] NFTSET-MATCH: domain: example.com, nftset: ip smartdns public_set, IP: 23.192.228.84
[2025-05-09 23:36:19,884][DEBUG][      fast_ping.c:679 ] ping [2600:1406:3a00:0021:0000:0000:173e:2e66]:443 end, id 128

@yingziwu
Copy link
Author

yingziwu commented May 9, 2025

可能影响比较大的选项是 ProtectSystem=strict(将整个文件系统挂载为只读),但需要写权限的地方,比如说 pidfile、日志、cache,也通过 RuntimeDirectoryLogsDirectoryCacheDirectory 获取了写权限。

@pymumu
Copy link
Owner

pymumu commented May 10, 2025

个人觉得保持和其他程序默认一致就可以了,上面列的大部分限制,smartdns都没有相关的代码。
特别是一些只读的设置,可能会导致某些用户异常,某些用户的日志,cache路径可能都被修改,并不是默认。
如果要安全,现在可以设置运行的用户为nobody,这样大部分权限都没有。

@yingziwu
Copy link
Author

个人觉得保持和其他程序默认一致就可以了,上面列的大部分限制,smartdns都没有相关的代码。

程序应当默认安全,尽可能最小权限,smartdns没有相关的功能,这不正意味着应用这些限制不会对程序产生影响。

如果要安全,现在可以设置运行的用户为nobody,这样大部分权限都没有。

使用非特权用户运行服务,这个PR就是这个目的。不过使用 nobody 已经是不推荐的做法,会被其它使用 nobody 运行的程序影响,这里使用 DynamicUser,运行服务时动态创建一个临时用户。

特别是一些只读的设置,可能会导致某些用户异常,某些用户的日志,cache路径可能都被修改,并不是默认。

ProtectSystem=strict 这个选项可以修改。
比如说修改为 true ,仅仅 /usr/, /boot, /efi 为只读。修改为 full,前面三个目录再加上 /etc 只读。另外可以使用 ReadWritePaths= 添加例外。

@pymumu
Copy link
Owner

pymumu commented May 10, 2025

安装后,几乎没有人去修改smartdns.service文件。
Linux其他服务的service文件也是一样。

默认的代码已经设置drop root权限的。
https://github.com/pymumu/smartdns/blob/master/src/utils/capbility.c#L77
也已经有相关的安全人员对smartdns做过攻击测试。

你那个修改设置的capbility还缺少NET_RAW权限,没这个icmp ping在某些系统上可能无法工作。
还有一个是data-dir参数,这个配置的路径,是用于存储WebUI的db文件的。
另外就是调度策略,为了ping的准确性,ping线程是设置了优先级,没这个权限,ping时延就可能有较大抖动。
最后一个能想到的,就是smartdns在启动的时候,会利用root权限创建相关的目录和其他需要的运行环境,之后再drop root权限。

所以对于这些修改,特别是目录只读权限的修改,这个影响目前未知。
目前已经很多发行版集成了smartdns,如果这个修改导致功能异常,后续这些配置会来回折腾。

安全和易用性就是双刃剑,找到适合的点即可,没必要过度设置。
所以除非特别明确有安全隐患和的配置项,不然我是不建议过度设置的,在加上代码上已经drop了root的大部分权限。
目前建议还是保持和其他systemd的unit配置保持一致即可。

最后,还是感谢你的测试和验证。

@pymumu pymumu force-pushed the master branch 3 times, most recently from 858ab9e to b547030 Compare August 14, 2025 15:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants