You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Linux下C语言实现以root权限重试加载内核模块的架构方案

权限提升与内核模块加载程序实现方案

一、权限检查与自动提权流程

  1. 程序启动时,通过cap_get_proc()检查是否具备CAP_SYS_MODULE权限:
    • 若具备,直接调用init_module()或finit_module()执行内核模块加载逻辑。
    • 若不具备,检查当前UID是否为0:
      • 是则直接返回错误(已为root但无权限属于异常场景)。
      • 否则,尝试通过sudo或pkexec重新启动自身,同时通过环境变量MY_MODULE_LOADER_ELEVATED=1标记提权后的执行模式。
  2. 提权后的进程启动时,先检查环境变量MY_MODULE_LOADER_ELEVATED:
    • 若存在,跳过提权流程,直接执行加载逻辑并将结果反馈给父进程。
    • 若不存在,进入正常权限检查流程。

二、父子进程可靠IPC实现(Unix域套接字)

针对文件描述符传递依赖执行链的问题,采用Unix域套接字实现稳定的父子进程通信,流程如下:

  1. 父进程(非root)创建临时Unix域套接字,路径设为/tmp/.module_loader_sock_<PID>(PID为父进程ID,避免路径冲突)。
  2. 父进程监听该套接字,等待子进程连接。
  3. 父进程通过sudo/pkexec启动自身时,将套接字路径通过环境变量MY_MODULE_LOADER_SOCK传递给子进程。
  4. 提权后的子进程读取环境变量中的套接字路径,连接该套接字。
  5. 子进程执行加载逻辑后,将结果(成功/失败状态、错误码等)通过套接字发送给父进程。
  6. 父进程接收结果后,关闭并删除套接字,根据结果处理错误后退出。

核心代码片段

父进程创建套接字并启动提权进程:

#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    // 创建Unix域套接字
    int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    struct sockaddr_un addr;
    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    snprintf(addr.sun_path, sizeof(addr.sun_path), "/tmp/.module_loader_sock_%d", getpid());
    unlink(addr.sun_path); // 确保路径无残留
    bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
    listen(sockfd, 1);

    // 设置环境变量传递套接字路径
    setenv("MY_MODULE_LOADER_SOCK", addr.sun_path, 1);
    setenv("MY_MODULE_LOADER_ELEVATED", "1", 1);

    // 优先尝试sudo,失败则用pkexec
    execlp("sudo", "sudo", "./module_loader", NULL);
    execlp("pkexec", "pkexec", "./module_loader", NULL);

    // 执行到此处说明提权工具调用失败
    perror("Failed to invoke sudo/pkexec");
    unlink(addr.sun_path);
    close(sockfd);
    return 1;
}

子进程发送执行结果:

#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <unistd.h>

int load_kernel_module() {
    // 实际加载逻辑,返回0表示成功,非0为错误码
    return 0;
}

int main() {
    if (getenv("MY_MODULE_LOADER_ELEVATED")) {
        const char* sock_path = getenv("MY_MODULE_LOADER_SOCK");
        if (!sock_path) exit(1);

        int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
        struct sockaddr_un addr;
        memset(&addr, 0, sizeof(addr));
        addr.sun_family = AF_UNIX;
        strncpy(addr.sun_path, sock_path, sizeof(addr.sun_path)-1);
        
        if (connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
            perror("Failed to connect to parent socket");
            exit(1);
        }

        int result = load_kernel_module();
        write(sockfd, &result, sizeof(result));

        close(sockfd);
        exit(result);
    }

    // 非提权模式下的权限检查逻辑...
    return 0;
}

三、内核模块与用户态私有IPC方案(Netlink)

若要避免在/dev下创建设备文件,Netlink是最优的私有IPC选择——它基于套接字机制,无需额外设备文件,且支持双向通信,仅需约定私有协议号即可实现专属通信通道:

  1. 内核模块注册Netlink私有协议(可选用12-31范围内的预留私有协议号,或动态分配)。
  2. 用户态程序创建Netlink套接字,与内核模块的协议号绑定。
  3. 双方通过nlmsg_*系列函数构造、解析消息,完成命令交互与结果传递。

内核模块核心代码

#include <linux/netlink.h>
#include <linux/socket.h>
#include <linux/module.h>

#define NETLINK_MY_MODULE 31 // 私有协议号

static struct sock *nl_sock;

static void handle_user_msg(struct sk_buff *skb) {
    struct nlmsghdr *nlh = nlmsg_hdr(skb);
    // 处理用户态发送的消息,执行对应逻辑
    // 构造并发送回复消息...
}

static int __init my_module_init(void) {
    nl_sock = netlink_kernel_create(&init_net, NETLINK_MY_MODULE, 0, handle_user_msg, NULL, THIS_MODULE);
    if (!nl_sock) return -ENOMEM;
    return 0;
}

static void __exit my_module_exit(void) {
    netlink_kernel_release(nl_sock);
}

module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");

用户态核心代码

#include <linux/netlink.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>

#define NETLINK_MY_MODULE 31
#define MAX_PAYLOAD 1024

int main() {
    int sockfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_MY_MODULE);
    struct sockaddr_nl addr;
    memset(&addr, 0, sizeof(addr));
    addr.nl_family = AF_NETLINK;
    addr.nl_pid = getpid();
    bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));

    // 构造发送到内核的消息
    struct nlmsghdr *nlh = (struct nlmsghdr*)malloc(NLMSG_SPACE(MAX_PAYLOAD));
    memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));
    nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
    nlh->nlmsg_pid = getpid();
    nlh->nlmsg_flags = 0;

    struct iovec iov;
    iov.iov_base = (void*)nlh;
    iov.iov_len = nlh->nlmsg_len;

    struct msghdr msg;
    memset(&msg, 0, sizeof(msg));
    msg.msg_name = (void*)&addr;
    msg.msg_namelen = sizeof(addr);
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;

    sendmsg(sockfd, &msg, 0);

    // 接收内核回复消息
    recvmsg(sockfd, &msg, 0);
    // 处理回复内容...

    free(nlh);
    close(sockfd);
    return 0;
}

内容的提问来源于stack exchange,提问作者Programmer_2147483647

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.02 05:13:11