在之前的文章:使用Socket发送非字符串数据中实现了socket发送非字符串数据的功能,我本以为是个完美的方案。但是在使用valgrind调试的时候发现了内存越界的问题。由于我是初学C,面对这个问题显得有些手足无措。还好经过了半天的试错,终于解决了。

问题出在这段代码上

    int pos = 0;
    int len = 0;

    while (pos < needSend)
    {
        len = send(sockfd, buffer + pos, BUFFER_SIZE, 0);
        if (len <= 0)
        {
            printf("ERROR");
            break;
        }
        pos += len;
    }

这段代码虽然通过buffer + pos来移动指针发送数据,但是没有计算剩余的数据量,每次都以BUFFER_SIZE大小发送。最后造成指针偏移超过buffer边界。使用gcc -g -fsanitize=address -fno-omit-frame-pointer -std=c11 -Wall -Wextra -Werror source.c -o target编译

=================================================================
==3775==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7f240122c104 at pc 0x7f24050dfc1e bp 0x7ffdd3797240 sp 0x7ffdd37969e8
WRITE of size 792 at 0x7f240122c104 thread T0
    #0 0x7f24050dfc1d  (/usr/lib/x86_64-linux-gnu/libasan.so.4+0x4ec1d)
    #1 0x55cad9e243ca in main /home/dongchen/桌面/socket/largeDataSend.c:180
    #2 0x7f2404cc1bf6 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21bf6)
    #3 0x55cad9e23fb9 in _start (/home/dongchen/桌面/socket/largeDataSend+0xfb9)

0x7f240122c104 is located 0 bytes to the right of 4000004-byte region [0x7f2400e5b800,0x7f240122c104)
allocated by thread T0 here:
    #0 0x7f240516fb40 in __interceptor_malloc (/usr/lib/x86_64-linux-gnu/libasan.so.4+0xdeb40)
    #1 0x55cad9e2438c in main /home/dongchen/桌面/socket/largeDataSend.c:175
    #2 0x7f2404cc1bf6 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21bf6)

SUMMARY: AddressSanitizer: heap-buffer-overflow (/usr/lib/x86_64-linux-gnu/libasan.so.4+0x4ec1d) 
Shadow bytes around the buggy address:
  0x0fe50023d7d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0fe50023d7e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0fe50023d7f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0fe50023d800: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0fe50023d810: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0fe50023d820:[04]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0fe50023d830: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0fe50023d840: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0fe50023d850: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0fe50023d860: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0fe50023d870: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==3775==ABORTING

接收到就是WRITE读取写入越界,发送端就是READ读取越界。

后来通过搜索找到了How to send messages with larger length than the buffer in socket programming?

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
int sendall(int s, char *buf, int *len)
{
    int total = 0;        // how many bytes we've sent
    int bytesleft = *len; // how many we have left to send
    int n;

    while(total < *len) {
        n = send(s, buf+total, bytesleft, 0);
        if (n == -1) { break; }
        total += n;
        bytesleft -= n;
    }

    *len = total; // return number actually sent here

    return n==-1?-1:0; // return -1 onm failure, 0 on success
}
#pragma GCC diagnostic pop

上面的#pragma部分是用来忽略使用gcc编译时提示 error: ‘n’ may be used uninitialized in this function [-Werror=maybe-uninitialized]这个报错的

通过计算剩余的bytesleft来保证内存不越界

其实lenn都是指实际发送的字节数或者实际收到的字节数。

放上没有内存泄漏的完整代码,两边都是Linux版本

服务端

#include <sys/types.h>
#include <sys/socket.h>                         // 包含套接字函数库
#include <stdio.h>
#include <netinet/in.h>                         // 包含AF_INET相关结构
#include <arpa/inet.h>                      // 包含AF_INET相关操作的函数
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>

#define HELLO_WORLD_SERVER_PORT    6666
#define LENGTH_OF_LISTEN_QUEUE     20

typedef struct
{
    int ab;
    int num[1000000];
}Node;

int recvall (int s,char *buf,int *len)
{
    int total = 0;
    int bytesleft = *len;
    int n;

    while (total<*len)
    {
        n= recv(s,buf+total,bytesleft,0);
        if(n==-1){break;}
        total += n;
        bytesleft -=n;
    }

    *len = total;
    return n==-1?-1:0;
}

int main(void)
{
    // set socket's address information
    struct sockaddr_in   server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htons(INADDR_ANY);
    server_addr.sin_port = htons(HELLO_WORLD_SERVER_PORT);

    // create a stream socket
    int server_socket = socket(PF_INET, SOCK_STREAM, 0);
    if (server_socket < 0)
    {
        printf("Create Socket Failed!\n");
        exit(1);
    }

    //bind
    if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)))
    {
        printf("Server Bind Port: %d Failed!\n", HELLO_WORLD_SERVER_PORT);
        exit(1);
    }

    // listen
    if (listen(server_socket, LENGTH_OF_LISTEN_QUEUE))
    {
        printf("Server Listen Failed!\n");
        exit(1);
    }

    while(1)
    {
        struct sockaddr_in client_addr;
        socklen_t   length = sizeof(client_addr);

        int new_server_socket = accept(server_socket, (struct sockaddr*)&client_addr, &length);
        if (new_server_socket < 0)
        {
            printf("Server Accept Failed!\n");
            break;
        }

        Node *myNode=(Node*)malloc(sizeof(Node));

        int needRecv=sizeof(Node);
        char *buffer=(char*)malloc(needRecv);
    
        int result = recvall(new_server_socket,buffer,&needRecv); 
        
        memcpy(myNode,buffer,needRecv);
        printf("recv over ab=%d num[0]=%d num[999999]=%d\n",myNode->ab,myNode->num[0],myNode->num[999999]);
        free(buffer);
        free(myNode);
        if(result ==0)
        {
            printf("Recv over!!!\n");
            break;
        }
    }
    close(server_socket);
    return 0;
}

客户端

#include <sys/types.h>
#include <sys/socket.h>                         // 包含套接字函数库
#include <stdio.h>
#include <netinet/in.h>                         // 包含AF_INET相关结构
#include <arpa/inet.h>                      // 包含AF_INET相关操作的函数
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>

#define MYPORT  6666

typedef struct
{
    int ab;
    int num[1000000];
}Node;

int sendall(int s, char *buf, int *len)
{
    int total = 0;
    int bytesleft = *len; 
    int n;

    while(total < *len) {
        n = send(s, buf+total, bytesleft, 0);
        if (n == -1) { break; }
        total += n;
        bytesleft -= n;
    }

    *len = total;

    return n==-1?-1:0;
} 

int main()
{
        ///sockfd
    int sock_cli = socket(AF_INET,SOCK_STREAM, 0);

    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(MYPORT);
    servaddr.sin_addr.s_addr = inet_addr("192.168.8.220");

    if (connect(sock_cli, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
    {
        perror("connect");
        exit(1);
    }

    Node *myNode=(Node*)malloc(sizeof(Node));
    myNode->ab=123;
    myNode->num[0]=110;
    myNode->num[999999]=99;

    int needSend=sizeof(Node);
    char *buffer=(char*)malloc(needSend);
    memcpy(buffer,myNode,needSend);

    int result = sendall(sock_cli, buffer,&needSend);
    if(result==0)
    {
        printf("Send over!!!\n");
    }
  
    free(buffer);
    free(myNode);
    close(sock_cli);
    
    return 0;
}

现在唯一的错误就是myNodenum[1]num[999998]没有初始化了。

标签: none

评论已关闭