SMB3内核服务器是Linux内核的一个组件。最初它作为实验性功能启用,但在内核版本6.6中移除了实验性标志并保持稳定状态。
Ksmbd通过任务分割实现性能优化:在内核空间处理关键文件操作,而通过ksmbd.mountd在用户空间处理非性能相关任务(如DCE/RPC和用户账户管理)。该服务器采用多线程架构,利用内核工作线程并行处理SMB请求以实现可扩展性,同时通过用户空间集成实现配置和RPC处理。
虽然ksmbd默认未启用,但它是学习SMB协议同时探索Linux内部机制(如网络、内存管理和线程)的理想研究对象。
ksmbd内核组件直接绑定445端口处理SMB流量。内核与ksmbd.mountd用户空间进程通过Netlink接口进行通信,这是Linux中基于套接字的内核-用户空间通信机制。ksmbd.mountd具有root权限,直接以内核为研究对象,因为其具有直接可达性。
架构示意图,如下所示:
关于该主题已有多项研究成果发表,包括Thalium和pwning.tech的研究。后者详细阐述了如何基于syzkaller从零开始进行模糊测试。
我们首先通过标准SMB客户端拦截并分析合法通信数据,据此扩展了syzkaller语法以涵盖smb2pdu.c中实现的附加命令。
在模糊测试过程中会遇到若干挑战,其中一项已在pwning.tech文章中提及。最初我们需要通过数据包标记来识别syzkaller实例(procid)。由于后续数据包共享同一套接字连接,仅需对首个数据包进行标记。我们通过在初始协商请求末尾追加8字节的实例编号来解决这个问题,后续数据包则无需标记。
syzkaller的另一局限是无法使用malloc()进行动态内存分配,这导致在伪系统调用中实现认证逻辑变得复杂。为此我们修补了相关的身份验证(NTLMv2)和数据包签名验证检查,从而无需有效签名即可绕过协商和会话建立流程。这使得我们能够调用更多命令(如ioctl处理逻辑)。
为创建更多样化且有效的测试用例,我们最初通过strace提取通信数据,或手动构造数据包。在此过程中我们使用了Kaitai Struct(通过其Web界面或可视化工具)。当数据包被内核拒绝时,Kaitai能帮助我们快速定位并解决问题。

在KSMBD初始化期间(无论是作为内核内置组件还是外部模块),系统会调用启动函数 create_socket() 来监听传入流量:
// https://elixir.bootlin.com/linux/v6.11/source/fs/smb/server/transport_tcp.c#L484
ret = kernel_listen(ksmbd_socket, KSMBD_SOCKET_BACKLOG);
if (ret) {
pr_err("Port listen() error: %d\n", ret);
goto out_error;
}
实际的数据处理发生在 ksmbd_tcp_new_connection() 函数及衍生的每个连接线程 (ksmbd:%u) 中。该函数还会分配代表连接的 struct ksmbd_conn 结构体:
// https://elixir.bootlin.com/linux/v6.11/source/fs/smb/server/transport_tcp.c#L203
static int ksmbd_tcp_new_connection(struct socket *client_sk)
{
// ..
handler = kthread_run(ksmbd_conn_handler_loop,
KSMBD_TRANS(t)->conn,
"ksmbd:%u",
ksmbd_tcp_get_port(csin));
// ..
}
ksmbd_conn_handler_loop 至关重要,它负责读取、验证和处理 SMB 协议消息(PDU)。在没有错误的情况下,它会调用更具体的处理函数之一:
// https://elixir.bootlin.com/linux/v6.11/source/fs/smb/server/connection.c#L395
if (default_conn_ops.process_fn(conn)) {
pr_err("Cannot handle request\n");
break;
}
处理函数会将SMB请求添加到工作线程队列中:
// ksmbd_server_process_request
static int ksmbd_server_process_request(struct ksmbd_conn *conn)
{
return queue_ksmbd_work(conn);
}
这一过程发生在queue_ksmbd_work函数内部,该函数会分配封装会话、连接及所有 SMB 相关数据的ksmbd_work结构体,同时执行早期初始化工作。
在 Linux 内核中,向工作队列添加工作项需要使用INIT_WORK()宏进行初始化,该宏会将工作项与处理时执行的回调函数相关联。具体实现如下所示:
// https://elixir.bootlin.com/linux/v6.11/source/fs/smb/server/server.c#L312
INIT_WORK(&work->work, handle_ksmbd_work);
ksmbd_queue_work(work);
现在我们已经接近处理SMB PDU操作的阶段。最后一步是由handle_ksmbd_work从请求中提取命令编号
// https://elixir.bootlin.com/linux/v6.11/source/fs/smb/server/server.c#L213
rc = __process_request(work, conn, &command);
并执行相应的命令处理程序。
// https://elixir.bootlin.com/linux/v6.11/source/fs/smb/server/server.c#L108
static int __process_request(struct ksmbd_work *work, struct ksmbd_conn *conn,
u16 *cmd)
{
// ..
command = conn->ops->get_cmd_val(work);
*cmd = command;
// ..
cmds =