简体   繁体   English

Linux 内核模块 unix 域套接字

[英]Linux Kernel Module unix domain sockets

I'm trying to share data between kernel and user space.我正在尝试在内核和用户空间之间共享数据。 The ultimate goal is to port it to Android, so I'm using unix domain sockets.最终目标是将其移植到 Android,因此我使用的是 unix 域套接字。 (I don't know if this is the best option). (我不知道这是否是最好的选择)。

I have a kernel module acting as socket client and ac program acting as socket server.我有一个充当套接字客户端的内核模块和充当套接字服务器的 ac 程序。 And vice-versa, a kernel module acting as socket server and ac program acting as socket client.反之亦然,内核模块充当套接字服务器,ac 程序充当套接字客户端。

Programs are very simple.程序非常简单。 Servers just send a string to clients and they print it.服务器只向客户端发送一个字符串,然后他们将其打印出来。

I run server_user.c and then I try to insert client_module.ko using insmod.我运行server_user.c ,然后尝试使用 insmod 插入client_module.ko I get the following error:我收到以下错误:

Kernel panic - not syncing: stack - protector: Kernel stack is corrupted in: ffffffffa0005157

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

What's wrong?怎么了?

Module Server模块服务器

#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");

Module Client模块客户端

#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");

User Server用户服务器

#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;
}

User Client用户客户端

#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;
}

I have just faced a similar issue (with UNIX datagrams though), and I believe there is a bug in the unix_mkname() function which is part of the kernel: this function makes sure the sun_path field of the given sockaddr_un structure is always null terminated by adding a NUL character after the end of that field.我刚刚遇到了类似的问题(尽管使用 UNIX 数据报),我相信作为内核一部分的unix_mkname()函数中存在一个错误:该函数确保给定sockaddr_un结构的sun_path字段始终以空值终止通过该字段的末尾添加一个 NUL 字符。 Since this is the last field, it will corrupt the area (stack or heap) where that structure has been allocated.由于这是最后一个字段,它将破坏分配该结构的区域(堆栈或堆)。

The best solution is to patch that function in the kernel source code, so that it only sets to NUL the last character of the sun_path field.最好的解决方案是在内核源代码中修补该函数,使其仅将sun_path字段的最后一个字符设置为 NUL。 Meanwhile, you can make your code work by decreasing by 1 byte the size you give for the sockaddr_un structure:同时,您可以通过将sockaddr_un结构的大小减少 1 个字节来使代码正常工作:

Module Server模块服务器

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

Module Client模块客户端

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

As I am using UNIX datagrams, I cannot really tell if it fixes the issue for bind() and connect() , but a least, it fixes it for sock_sendmsg() .当我使用 UNIX 数据报时,我无法确定它是否修复了bind()connect() ,但至少,它修复了sock_sendmsg()

@shu-suzuki: thanks for the leads. @shu-suzuki:感谢您的指导。

After 5 years I came back here... I again stucked with this problem and found that I did the same thing 5 years ago. 5 年后我回到这里......我再次陷入这个问题,发现我在 5 年前做了同样的事情。

The cause is as in Tey's answer and the solution is OK (sutbract 1 from the length) but here's another solution.原因与 Tey 的答案相同,解决方案还可以(从长度减去 1),但这是另一种解决方案。

What you can do is to use struct sockaddr_storage as struct sockaddr_un .您可以做的是使用struct sockaddr_storage作为struct sockaddr_un As far as I read the kernel code this is what they use to handle bind system call from user space.就我读过的内核代码而言,这就是他们用来处理来自用户空间的绑定系统调用的内容。 sockeaddr_storage is larger than sockaddr_un so kernel code can write beyond the sizeof(sockaddr_un). sockeaddr_storagesockaddr_un因此内核代码可以写入超过 sizeof(sockaddr_un)。

The code should be like代码应该像

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));

This looks working for me.这看起来对我有用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM