Linux内核模块Unix域套接字

3
我正在尝试在内核空间和用户空间之间共享数据。最终目标是将其移植到Android上,因此我正在使用Unix域套接字。(我不确定这是否是最佳选项)
我有一个充当套接字客户端的内核模块和一个充当套接字服务器的C程序。反过来,我还有一个充当套接字服务器的内核模块和一个充当套接字客户端的C程序。
程序非常简单。服务器只需向客户端发送一个字符串,然后它们就会打印出来。
我运行了server_user.c,然后尝试使用insmod插入client_module.ko,但我收到以下错误:
Kernel panic - not syncing: stack - protector: Kernel stack is corrupted in: ffffffffa0005157

drm-kms-helper: panic occurred, switching back to text console 

有什么问题吗?

模块服务器

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/socket.h>
#include <linux/net.h>
#include <linux/un.h>
#include <net/sock.h>

#define SOCK_PATH   "/tmp/usocket"
#define LISTEN      10

struct socket *sock = NULL;
struct socket *newsock = NULL;

static int __init server_module_init( void ) {

  int retval;
  char* string = "hello_world";

  struct sockaddr_un addr;
  struct msghdr msg;
  struct iovec iov;
  mm_segment_t oldfs;

  // create
  retval = sock_create(AF_UNIX, SOCK_STREAM, 0, &sock);

  memset(&addr, 0, sizeof(addr));
  addr.sun_family = AF_UNIX;
  strcpy(addr.sun_path, SOCK_PATH);

  // bind
  retval = sock->ops->bind(sock,(struct sockaddr *)&addr, sizeof(addr));

  // listen
  retval = sock->ops->listen(sock, LISTEN);

  //accept
  retval = sock->ops->accept(sock, newsock, 0);

  //sendmsg
  memset(&msg, 0, sizeof(msg));
  memset(&iov, 0, sizeof(iov));

  msg.msg_name = 0;
  msg.msg_namelen = 0;
  msg.msg_iov = &iov;
  msg.msg_iov->iov_base = string;
  msg.msg_iov->iov_len = strlen(string)+1;
  msg.msg_iovlen = 1;
  msg.msg_control = NULL;
  msg.msg_controllen = 0;
  msg.msg_flags = 0;

  oldfs = get_fs();
  set_fs(KERNEL_DS);

  retval = sock_sendmsg(newsock, &msg, strlen(string)+1);

  set_fs(oldfs);

  return 0;
}

static void __exit server_module_exit( void ) {
  printk(KERN_INFO "Exit usocket.");
}

module_init( server_module_init );
module_exit( server_module_exit );
MODULE_LICENSE("GPL");

客户端模块

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/un.h>
#include <linux/net.h>
#include <net/sock.h>
#include <linux/socket.h>

#define SOCK_PATH   "/tmp/usocket"
#define MAX     100

struct socket *sock = NULL;


static int __init client_module_init( void ) {

  int retval;
  char str[MAX];

  struct sockaddr_un addr;
  struct msghdr msg;
  struct iovec iov;
  mm_segment_t oldfs;

  printk(KERN_INFO "Start client module.\n");

  // create
  retval = sock_create(AF_UNIX, SOCK_STREAM, 0, &sock); 

  // connect
  memset(&addr, 0, sizeof(addr));  
  addr.sun_family = AF_UNIX;
  strcpy(addr.sun_path, SOCK_PATH);

  retval = sock->ops->connect(sock, (struct sockaddr *)&addr, sizeof(addr), 0);

  // recvmsg

  memset(&msg, 0, sizeof(msg));
  memset(&iov, 0, sizeof(iov));

  msg.msg_name = 0;
  msg.msg_namelen = 0;
  msg.msg_iov = &iov;
  msg.msg_iovlen = 1;
  msg.msg_iov->iov_base= str;
  msg.msg_iov->iov_len= strlen(str)+1;
  msg.msg_control = NULL;
  msg.msg_controllen = 0;
  msg.msg_flags = 0;

  oldfs = get_fs();
  set_fs(KERNEL_DS);

  retval = sock_recvmsg(sock, &msg, strlen(str)+1, 0);

  set_fs(oldfs);

  // print str
  printk(KERN_INFO "client module: %s.\n",str);

  // release socket
  sock_release(sock);

  return 0;
}

static void __exit client_module_exit( void )
{
  printk(KERN_INFO "Exit client module.\n");
}

 module_init( client_module_init );
 module_exit( client_module_exit );
 MODULE_LICENSE("GPL");

用户服务器

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

#define SOCK_PATH "/tmp/usocket"

int send_msg_to_client(int socketfd, char* data) {

  struct msghdr msg;
  struct iovec iov;
  int s;

  memset(&msg, 0, sizeof(msg));
  memset(&iov, 0, sizeof(iov));

  msg.msg_name = NULL;
  msg.msg_namelen = 0;
  iov.iov_base = data;
  iov.iov_len = strlen(data)+1;
  msg.msg_iov = &iov;
  msg.msg_iovlen = 1;
  msg.msg_control = NULL;
  msg.msg_controllen = 0;
  msg.msg_flags = 0;

  s = sendmsg(socketfd, &msg, 0);

  printf("after send - iov_base: %s, length: %d\n", (char *) msg.msg_iov->iov_base, (int) strlen(msg.msg_iov->iov_base));

  if(s < 0){
    perror("sendmsg");
    return 0;
  }

  return s;
}


int main(int argc, char* argv[])
{

        if (argc != 2) {
          printf("Usage: $ %s <string>\n",argv[0]);
          return 0;
        }

    int s, s2, len, slen;
    socklen_t t;
    struct sockaddr_un local, remote;
    char* data = argv[1];

    printf("print data: %s\n",data);

    if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        exit(1);
    }

    memset(&local, 0, sizeof(local));
    local.sun_family = AF_UNIX;
    strcpy(local.sun_path, SOCK_PATH);

    unlink(local.sun_path);

    len = strlen(local.sun_path) + sizeof(local.sun_family);
    if (bind(s, (struct sockaddr *)&local, len) == -1) {
        perror("bind");
        exit(1);
    }

    if (listen(s, 5) == -1) {
        perror("listen");
        exit(1);
    }

    printf("Waiting for a connection...\n");

    t = sizeof(remote);
    if ((s2 = accept(s, (struct sockaddr *)&remote, &t)) == -1) {
        perror("accept");
        exit(1);
    }

    printf("Connected.\n");

    slen = send_msg_to_client(s2, data);

    if(slen < 0)
        perror("send");

    close(s2);

    return 0;
}

用户客户端

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

#define SOCK_PATH "/tmp/usocket"
#define MAX 100

int recv_msg_from_server(int socketfd, char data[MAX]) {

  struct msghdr msg;
  struct iovec iov;
  int s;

  memset(&msg, 0, sizeof(msg));
  memset(&iov, 0, sizeof(iov));

  msg.msg_name = NULL;
  msg.msg_namelen = 0;
  iov.iov_base = data;
  iov.iov_len = MAX;
  msg.msg_iov = &iov;
  msg.msg_iovlen = 1;
  msg.msg_control = NULL;
  msg.msg_controllen = 0;
  msg.msg_flags = 0;


  s = recvmsg(socketfd, &msg, 0);

  printf("after recv - iov_base: %s, length: %d\n", (char *) msg.msg_iov->iov_base, (int) strlen(msg.msg_iov->iov_base));

  if(s < 0){
    perror("recvmsg");
  }

  return s;
}


int main(void)
{
    int s, len, slen;
    struct sockaddr_un remote;
    char data[MAX];

    if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
            perror("socket");
            exit(1);
        }

    printf("Trying to connect...\n");

    memset(&remote, 0, sizeof(remote));

    remote.sun_family = AF_UNIX;
    strcpy(remote.sun_path, SOCK_PATH);
    len = strlen(remote.sun_path) + sizeof(remote.sun_family);

    if (connect(s, (struct sockaddr *)&remote, len) == -1) {
            perror("connect");
            exit(1);
        }

    printf("Connected.\n");

    slen = recv_msg_from_server(s, data);

    if (slen < 0) {
        perror("recvmsg");
    }

    //printf("print data received > %s\n", data);

    close(s);

    return 0;
}

也许在恐慌发生时包含内核日志的输出会有所帮助。如果没有它,你就要求别人去猜测或尝试构建你的模块并复制你的结果。 - mah
我尝试了同样的事情,并且得到了相同的错误(在调用创建套接字上的connect时)。经过多次尝试,我发现如果我通过kmalloc在内核堆中分配sockaddr_un结构,则它似乎可以工作,尽管我不知道为什么。有人知道为什么它能够工作吗?(可能像我的堆也被破坏了,但因缺乏而工作...) - Shu Suzuki
1
我也尝试添加-fno-stack-protector编译器标志以避免堆栈检查。这也避免了错误(显然,因为错误发生在检查中)。我仍然没有足够的信心,不确定堆栈检查是否正确... - Shu Suzuki
2个回答

3
我刚遇到了一个类似的问题(尽管是关于UNIX数据报),我认为内核中的unix_mkname()函数存在一个错误:该函数通过在该字段的末尾添加NUL字符之后,确保给定的sockaddr_un结构体的sun_path字段始终以null结尾。由于这是最后一个字段,它将破坏分配该结构体的区域(堆栈或堆)。

最好的解决方案是在内核源代码中修补该函数,使其仅将sun_path字段的最后一个字符设置为NUL。同时,您可以通过减少sockaddr_un结构体的大小1个字节来使您的代码正常工作:

模块服务器

  // bind
  retval = sock->ops->bind(sock,(struct sockaddr *)&addr, sizeof(addr) - 1);

客户端模块

 retval = sock->ops->connect(sock, (struct sockaddr *)&addr, sizeof(addr) - 1, 0);

由于我正在使用UNIX数据报,因此我无法确定它是否修复了bind()connect()的问题,但至少它修复了sock_sendmsg()的问题。
@shu-suzuki:感谢您的线索。

1

五年后我又回到这里......我再次陷入了同样的问题,发现我五年前做过同样的事情。

原因和Tey的答案一样,解决方案也可以(从长度中减去1),但这里有另一个解决方案。

你可以使用struct sockaddr_storage作为struct sockaddr_un。据我所读的内核代码,这是他们用来处理用户空间绑定系统调用的方法。sockeaddr_storagesockaddr_un大,所以内核代码可以在sizeof(sockaddr_un)之外写入。

代码应该像这样:

struct sockaddr_storage addrStorage;
struct sockaddr_un* addr;
memset(&addrStorage, 0, sizeof(addrStorage));
addr = (struct sockaddr_un*)&addrStorage;
addr->sun_family = AF_UNIX;
strcpy(addr->sun_path, "/my/sock/name");
kernel_bind(listenSock, (struct sockaddr*)addr, sizeof(*addr));

这对我来说看起来是有效的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接