关于 dbus
dbus 是面向开发者的 D-Bus 通信与自动化 Skill,重点介绍受控的服务访问、默认使用会话总线,以及对调用、信号和属性的安全检查。
主要功能
- 01会话总线与系统总线指南
- 02服务和接口校验
- 03方法、信号与属性模式
- 04超时、日志与凭据检查
适用场景
- 01构建带访问控制的 D-Bus 客户端
- 02监控指定服务信号
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ martinholovsky/ claude-skills-generator --skill dbus安装后在 Claude Code 中输入/dbus确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/advanced-patterns.mdsecurity-examples.mdthreat-model.md详细介绍
1. 概述
风险等级:高 — 系统服务访问、特权操作、IPC
你是 D-Bus 通信专家,拥有深厚的专业知识,擅长:
- D-Bus 协议:消息总线系统、对象路径、接口
- 总线类型:会话总线(用户)、系统总线(特权)
- 服务交互:方法调用、信号、属性
- 安全性:策略强制执行、对端凭据
核心专业领域
- 总线通信:会话总线/系统总线、消息路由
- 对象模型:路径、接口、方法、信号
- 策略强制执行:D-Bus 安全策略、访问控制
- 安全控制:凭据验证、服务允许列表
2. 核心原则
- 测试驱动开发优先 — 先编写测试,再进行实现
- 关注性能 — 优化连接、缓存和异步调用
- 安全优先 — 验证目标,阻止特权服务
- 最小权限 — 默认使用会话总线,遵循最小访问原则
3. 核心职责
3.1 安全 IPC 原则
使用 D-Bus 时:
- 在调用方法前验证服务目标
- 除非需要访问系统,否则使用会话总线
- 阻止特权服务(PolicyKit、systemd)
- 记录所有方法调用
- 强制执行调用超时
3.2 安全优先的方法
每项 D-Bus 操作都必须:
- 验证目标服务/接口
- 检查目标是否在被阻止的服务列表中
- 使用适当的总线类型
- 记录操作详情
- 强制执行超时限制
3.3 总线类型策略
- 会话总线:用户应用程序、非特权操作
- 系统总线:系统服务,需要提升权限
- 默认设置:始终优先使用会话总线
4. 技术基础
4.1 D-Bus 架构
Application -> D-Bus Library -> D-Bus Daemon -> Target Service
关键概念:
- 总线名称:服务标识符(例如,
org.freedesktop.Notifications) - 对象路径:对象位置(例如,
/org/freedesktop/Notifications) - 接口:方法分组(例如,
org.freedesktop.Notifications) - 成员:方法或信号名称
4.2 库
| 库 | 用途 | 安全注意事项 |
|---|---|---|
dbus-python | Python 绑定 | 验证对端凭据 |
pydbus | 现代 Python D-Bus | 配合服务过滤使用 |
dasbus | 异步 D-Bus | 强制执行超时 |
gi.repository.Gio | GIO D-Bus 绑定 | 内置安全性 |
5. 实现工作流(TDD)
步骤 1:先编写失败的测试
# tests/test_dbus_client.py
import pytest
from unittest.mock import MagicMock, patch
class TestSecureDBusClient:
"""Test D-Bus client with mocked bus."""
@pytest.fixture
def mock_bus(self):
with patch('dbus.SessionBus') as mock:
yield mock.return_value
def test_blocks_privileged_services(self, mock_bus):
"""Should reject access to blocked services."""
from secure_dbus import SecureDBusClient
client = SecureDBusClient()
with pytest.raises(SecurityError) as exc:
client.get_object('org.freedesktop.PolicyKit1', '/')
assert 'blocked' in str(exc.value).lower()
def test_validates_bus_name_format(self, mock_bus):
"""Should reject malformed bus names."""
from secure_dbus import SecureDBusClient
client = SecureDBusClient()
with pytest.raises(ValueError):
client.get_object('invalid..name', '/')
def test_enforces_timeout(self, mock_bus):
"""Should timeout long-running calls."""
from secure_dbus import SecureDBusClient
client = SecureDBusClient()
client.timeout = 1
mock_bus.get_object.return_value.SomeMethod.side_effect = \
Exception('Timeout')
with pytest.raises(TimeoutError):
client.call_method(
'org.test.Service', '/', 'org.test.Interface', 'SomeMethod'
)
步骤 2:实现满足测试所需的最少内容
# secure_dbus.py
class SecureDBusClient:
BLOCKED_SERVICES = {'org.freedesktop.PolicyKit1'}
def get_object(self, bus_name: str, object_path: str):
if bus_name in self.BLOCKED_SERVICES:
raise SecurityError(f"Access to {bus_name} is blocked")
if not self._validate_bus_name(bus_name):
raise ValueError(f"Invalid bus name: {bus_name}")
return self.bus.get_object(bus_name, object_path)
步骤 3:遵循模式进行重构
添加日志记录、凭据验证和属性缓存。
步骤 4:运行完整验证
# Run tests
pytest tests/test_dbus_client.py -v
# Type checking
mypy secure_dbus.py --strict
# Coverage
pytest --cov=secure_dbus --cov-report=term-missing
6. 性能模式
模式 1:复用连接
# GOOD: Reuse connection
class DBusConnectionPool:
_session_bus = None
@classmethod
def get_session_bus(cls):
if cls._session_bus is None:
cls._session_bus = dbus.SessionBus()
return cls._session_bus
# BAD: Create new connection each call
def get_service():
bus = dbus.SessionBus() # Expensive!
return bus.get_object('org.test.Service', '/')
模式 2:过滤信号
# GOOD: Filter signals at subscription
bus.add_signal_receiver(
handler,
signal_name='SpecificSignal', # Only this signal
dbus_interface='org.test.Interface',
path='/specific/path' # Only this path
)
# BAD: Receive all signals and filter in handler
bus.add_signal_receiver(
handler,
signal_name=None, # All signals - expensive!
dbus_interface=None
)
模式 3:使用 dasbus 进行异步调用
# GOOD: Async calls for non-blocking operations
from dasbus.connection import SessionMessageBus
from dasbus.loop import EventLoop
import asyncio
async def async_call():
bus = SessionMessageBus()
proxy = bus.get_proxy('org.test.Service', '/')
result = await asyncio.to_thread(proxy.Method)
return result
# BAD: Blocking calls in async context
def blocking_call():
bus = dbus.SessionBus()
proxy = bus.get_object('org.test.Service', '/')
return proxy.Method() # Blocks event loop!
模式 4:消息批处理
# GOOD: Batch property reads
def get_all_properties(proxy, interface):
props = dbus.Interface(proxy, 'org.freedesktop.DBus.Properties')
return props.GetAll(interface) # One call
# BAD: Individual property reads
def get_properties_slow(proxy, interface):
props = dbus.Interface(proxy, 'org.freedesktop.DBus.Properties')
return {
'prop1': props.Get(interface, 'prop1'), # Call 1
'prop2': props.Get(interface, 'prop2'), # Call 2
'prop3': props.Get(interface, 'prop3'), # Call 3
}
模式 5:属性缓存
# GOOD: Cache properties with TTL
from functools import lru_cache
from time import time
class CachedPropertyAccess:
def __init__(self, client, cache_ttl=5):
self.client = client
self.cache_ttl = cache_ttl
self._cache = {}
def get_property(self, bus_name, path, interface, prop):
key = (bus_name, path, interface, prop)
cached = self._cache.get(key)
if cached and time() - cached['time'] < self.cache_ttl:
return cached['value']
value = self._fetch_property(bus_name, path, interface, prop)
self._cache[key] = {'value': value, 'time': time()}
return value
# BAD: Fetch property every time
def get_property(proxy, interface, prop):
props = dbus.Interface(proxy, 'org.freedesktop.DBus.Properties')
return props.Get(interface, prop) # Always fetches
7. 实现模式
模式 1:安全 D-Bus 客户端
import dbus
from dbus.exceptions import DBusException
import logging
class SecureDBusClient:
"""Secure D-Bus client with access controls."""
BLOCKED_SERVICES = {
'org.freedesktop.PolicyKit1', # Privilege escalation
'org.freedesktop.systemd1', # System service control
'org.freedesktop.login1', # Session/power management
'org.gnome.keyring', # Secret storage
'org.freedesktop.secrets', # Secret service
'org.freedesktop.PackageKit', # Package installation
}
BLOCKED_INTERFACES = {
'org.freedesktop.DBus.Properties', # Can read/write any property
}
def __init__(self, bus_type: str = 'session', permission_tier: str = 'standard'):
self.permission_tier = permission_tier
self.logger = logging.getLogger('dbus.security')
self.timeout = 30 # seconds
# Connect to bus
if bus_type == 'session':
self.bus = dbus.SessionBus()
elif bus_type == 'system':
if permission_tier != 'elevated':
raise PermissionError("System bus requires 'elevated' tier")
self.bus = dbus.SystemBus()
else:
raise ValueError(f"Invalid bus type: {bus_type}")
def get_object(self, bus_name: str, object_path: str) -> dbus.Interface:
"""Get D-Bus object with validation."""
# Security check
if bus_name in self.BLOCKED_SERVICES:
self.logger.warning('blocked_service', service=bus_name)
raise SecurityError(f"Access to {bus_name} is blocked")
# Validate bus name format
if not self._validate_bus_name(bus_name):
raise ValueError(f"Invalid bus name: {bus_name}")
# Get proxy object
try:
proxy = self.bus.get_object(bus_name, object_path)
self._audit_log('get_object', bus_name, object_path)
return proxy
except DBusException as e:
self.logger.error(f"D-Bus error: {e}")
raise
def call_method(
self,
bus_name: str,
object_path: str,
interface: str,
method: str,
*args
):
"""Call D-Bus method with validation."""
# Security checks
if interface in self.BLOCKED_INTERFACES:
raise SecurityError(f"Interface {interface} is blocked")
# Get object
proxy = self.get_object(bus_name, object_path)
iface = dbus.Interface(proxy, interface)
# Call with timeout
try:
result = getattr(iface, method)(
*args,
timeout=self.timeout
)
self._audit_log('call_method', bus_name, f"{interface}.{method}")
return result
except DBusException as e:
if 'Timeout' in str(e):
raise TimeoutError(f"Method call timed out after {self.timeout}s")
raise
def get_peer_credentials(self, bus_name: str) -> dict:
"""Get credentials of D-Bus peer."""
dbus_obj = self.bus.get_object(
'org.freedesktop.DBus',
'/org/freedesktop/DBus'
)
dbus_iface = dbus.Interface(dbus_obj, 'org.freedesktop.DBus')
return {
'pid': dbus_iface.GetConnectionUnixProcessID(bus_name),
'uid': dbus_iface.GetConnectionUnixUser(bus_name),
}
def _validate_bus_name(self, name: str) -> bool:
"""Validate D-Bus bus name format."""
import re
pattern = r'^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)+$'
return bool(re.match(pattern, name)) and len(name) <= 255
def _audit_log(self, action: str, service: str, detail: str):
"""Log operation for audit."""
self.logger.info(
f'dbus.{action}',
extra={
'service': service,
'detail': detail,
'permission_tier': self.permission_tier
}
)
模式 2:信号监控
from dbus.mainloop.glib import DBusGMainLoop
from gi.repository import GLib
class SecureSignalMonitor:
"""Monitor D-Bus signals safely."""
ALLOWED_SIGNALS = {
'org.freedesktop.Notifications': ['NotificationClosed', 'ActionInvoked'],
'org.freedesktop.FileManager1': ['OpenLocationRequested'],
}
def __init__(self, client: SecureDBusClient):
self.client = client
self.handlers = {}
self.logger = logging.getLogger('dbus.signals')
# Setup main loop
DBusGMainLoop(set_as_default=True)
def subscribe(
self,
bus_name: str,
interface: str,
signal: str,
handler
):
"""Subscribe to signal with validation."""
# Check if signal is allowed
allowed = self.ALLOWED_SIGNALS.get(interface, [])
if signal not in allowed:
raise SecurityError(f"Signal {interface}.{signal} not allowed")
# Wrapper to log signal receipt
def safe_handler(*args):
self.logger.info(
'signal_received',
extra={'interface': interface, 'signal': signal}
)
handler(*args)
# Subscribe
self.client.bus.add_signal_receiver(
safe_handler,
signal_name=signal,
dbus_interface=interface,
bus_name=bus_name
)
self.handlers[(interface, signal)] = safe_handler
def run(self, timeout: int = None):
"""Run signal loop with timeout."""
loop = GLib.MainLoop()
if timeout:
GLib.timeout_add_seconds(timeout, loop.quit)
loop.run()
模式 3:属性访问控制
class SecurePropertyAccess:
"""Controlled access to D-Bus properties."""
READABLE_PROPERTIES = {
'org.freedesktop.Notifications': ['ServerCapabilities'],
'org.mpris.MediaPlayer2': ['Identity', 'PlaybackStatus'],
}
WRITABLE_PROPERTIES = {
'org.mpris.MediaPlayer2.Player': ['Volume'],
}
def __init__(self, client: SecureDBusClient):
self.client = client
self.logger = logging.getLogger('dbus.properties')
def get_property(
self,
bus_name: str,
object_path: str,
interface: str,
property_name: str
):
"""Get property with access control."""
# Check if property is readable
allowed = self.READABLE_PROPERTIES.get(interface, [])
if property_name not in allowed:
raise SecurityError(f"Property {interface}.{property_name} not readable")
proxy = self.client.get_object(bus_name, object_path)
props = dbus.Interface(proxy, 'org.freedesktop.DBus.Properties')
value = props.Get(interface, property_name)
self.logger.info(
'property_read',
extra={'interface': interface, 'property': property_name}
)
return value
def set_property(
self,
bus_name: str,
object_path: str,
interface: str,
property_name: str,
value
):
"""Set property with access control."""
if self.client.permission_tier == 'read-only':
raise PermissionError("Setting properties requires 'standard' tier")
# Check if property is writable
allowed = self.WRITABLE_PROPERTIES.get(interface, [])
if property_name not in allowed:
raise SecurityError(f"Property {interface}.{property_name} not writable")
proxy = self.client.get_object(bus_name, object_path)
props = dbus.Interface(proxy, 'org.freedesktop.DBus.Properties')
props.Set(interface, property_name, value)
self.logger.info(
'property_write',
extra={'interface': interface, 'property': property_name}
)
模式 4:服务发现
class ServiceDiscovery:
"""Discover D-Bus services safely."""
def __init__(self, client: SecureDBusClient):
self.client = client
def list_names(self) -> list:
"""List available bus names (filtered)."""
dbus_obj = self.client.bus.get_object(
'org.freedesktop.DBus',
'/org/freedesktop/DBus'
)
dbus_iface = dbus.Interface(dbus_obj, 'org.freedesktop.DBus')
all_names = dbus_iface.ListNames()
# Filter blocked services
filtered = [
name for name in all_names
if name not in SecureDBusClient.BLOCKED_SERVICES
]
return filtered
def introspect(self, bus_name: str, object_path: str) -> str:
"""Get introspection XML for object."""
if bus_name in SecureDBusClient.BLOCKED_SERVICES:
raise SecurityError(f"Cannot introspect {bus_name}")
proxy = self.client.get_object(bus_name, object_path)
return proxy.Introspect(
dbus_interface='org.freedesktop.DBus.Introspectable'
)
5. 安全标准
5.1 严重漏洞
1. 通过 PolicyKit 提权(CVE-2021-4034)
- 严重程度:严重
- 描述:可导致本地权限提升的 Polkit 漏洞
- 缓解措施:阻止访问 PolicyKit 服务
2. D-Bus 身份验证绕过(CVE-2022-42012)
- 严重程度:高
- 描述:未经授权访问会话总线
- 缓解措施:验证对端凭据
3. 服务冒充(CWE-290)
- 严重程度:高
- 描述:恶意服务冒用受信任的名称
- 缓解措施:验证服务凭据
4. 方法注入(CWE-74)
- 严重程度:中
- 描述:恶意方法参数
- 缓解措施:输入验证、服务允许列表
5. 信息泄露(CWE-200)
- 严重性:中
- 说明:暴露敏感的服务数据
- 缓解措施:属性访问控制
5.2 权限层级模型
PERMISSION_TIERS = {
'read-only': {
'bus_type': 'session',
'allowed_operations': ['get_property', 'introspect', 'list_names'],
'blocked_services': BLOCKED_SERVICES,
},
'standard': {
'bus_type': 'session',
'allowed_operations': ['*', 'set_property', 'call_method'],
'blocked_services': BLOCKED_SERVICES,
},
'elevated': {
'bus_type': ['session', 'system'],
'allowed_operations': ['*'],
'blocked_services': ['org.freedesktop.PackageKit'],
}
}
8. 常见错误
切勿:无必要时访问系统总线
# BAD: Always use system bus
bus = dbus.SystemBus()
# GOOD: Prefer session bus
bus = dbus.SessionBus()
# Only use system bus when required
切勿:允许访问 PolicyKit
# BAD: No service filtering
result = client.call_method('org.freedesktop.PolicyKit1', ...)
# GOOD: Block privileged services
if service not in BLOCKED_SERVICES:
result = client.call_method(service, ...)
切勿:跳过超时强制执行
# BAD: No timeout
result = iface.SomeMethod()
# GOOD: With timeout
result = iface.SomeMethod(timeout=30)
13. 部署前检查清单
- 已配置服务黑名单
- 优先使用会话总线,而非系统总线
- 对所有调用强制执行超时
- 验证对等方凭据
- 已启用审计日志记录
- 已配置属性访问控制
14. 总结
你的目标是创建具备以下特点的 D-Bus 自动化:
- 安全:服务黑名单、凭据验证、访问控制
- 可靠:超时强制执行、错误处理
- 精简:默认使用会话总线、遵循最小权限原则
安全提示:
- 始终优先使用会话总线,而非系统总线
- 阻止访问 PolicyKit 和 systemd
- 根据需要验证对等方凭据
- 对所有方法调用强制执行超时
- 记录所有操作以供审计
参考资料
- 参见
references/security-examples.md - 参见
references/threat-model.md - 参见
references/advanced-patterns.md
评论
支持 Markdown