The following code generates a segmentation fault on iteration 33. The offending line of code is memcpy(data, datagram, 4096);
. How could a simple memcpy
generate a segmentation fault?
The output of my program in gdb is as follows:
(gdb) r
Starting program: /home/yaakov/mmap/minimal
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Iteration 0
Iteration 1
Iteration 2
Iteration 3
Iteration 4
Iteration 5
Iteration 6
Iteration 7
Iteration 8
Iteration 9
Iteration 10
Iteration 11
Iteration 12
Iteration 13
Iteration 14
Iteration 15
Iteration 16
Iteration 17
Iteration 18
Iteration 19
Iteration 20
Iteration 21
Iteration 22
Iteration 23
Iteration 24
Iteration 25
Iteration 26
Iteration 27
Iteration 28
Iteration 29
Iteration 30
Iteration 31
Iteration 32
Iteration 33
Program received signal SIGSEGV, Segmentation fault.
0x00005555555556e0 in main (argc=1, argv=0x7fffffffe618) at mmap/minimal.c:151
151 memcpy(data, datagram, 4096);
(gdb) bt
#0 0x00005555555556e0 in main (argc=1, argv=0x7fffffffe618) at mmap/minimal.c:151
The minimal reproducer which produces this segmentation fault is as follows:
#include <string.h>
#include <sys/mman.h>
#include <stdint.h>
#include <sys/socket.h>
#include <features.h> /* for the glibc version number */
#if __GLIBC__ >= 2 && __GLIBC_MINOR >= 1
#include <netpacket/packet.h>
#include <net/ethernet.h> /* the L2 protocols */
#else
#include <asm/types.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h> /* The L2 protocols */
#endif
#include <arpa/inet.h>
#include <stdio.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <net/if.h>
volatile struct tpacket_hdr * ps_header_start;
volatile int fd_socket;
volatile int data_offset = 0;
volatile struct sockaddr_ll *ps_sockaddr = NULL;
static int mode_loss = 0;
static char * str_devname= NULL;
static int c_buffer_sz = 1024*8;
static int c_buffer_nb = 1024;
static int c_sndbuf_sz = 0;
struct tpacket_req s_packet_req;
int main( int argc, char ** argv )
{
uint32_t size;
struct sockaddr_ll my_addr;
struct ifreq s_ifr;
int i_index = 0;
int ec;
int i_ifindex;
int tmp;
fd_socket = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if(fd_socket == -1)
{
perror("socket");
return 1;
}
/* clear structure */
memset(&my_addr, 0, sizeof(struct sockaddr_ll));
my_addr.sll_family = PF_PACKET;
my_addr.sll_protocol = htons(ETH_P_ALL);
str_devname = "enp3s0";
//strcpy (str_devname, ifname);
/* initialize interface struct */
strncpy (s_ifr.ifr_name, str_devname, sizeof(s_ifr.ifr_name));
/* Get the broad cast address */
ec = ioctl(fd_socket, SIOCGIFINDEX, &s_ifr);
if(ec == -1)
{
perror("iotcl");
return 1;
}
/* update with interface index */
i_ifindex = s_ifr.ifr_ifindex;
s_ifr.ifr_mtu = 7200;
/* update the mtu through ioctl */
ec = ioctl(fd_socket, SIOCSIFMTU, &s_ifr);
if(ec == -1)
{
perror("iotcl");
return 1;
}
/* set sockaddr info */
memset(&my_addr, 0, sizeof(struct sockaddr_ll));
my_addr.sll_family = AF_PACKET;
my_addr.sll_protocol = ETH_P_ALL;
my_addr.sll_ifindex = i_ifindex;
/* bind port */
if (bind(fd_socket, (struct sockaddr *)&my_addr, sizeof(struct sockaddr_ll)) == -1)
{
perror("bind");
return 1;
}
/* prepare Tx ring request */
s_packet_req.tp_block_size = c_buffer_sz;
s_packet_req.tp_frame_size = c_buffer_sz;
s_packet_req.tp_block_nr = c_buffer_nb;
s_packet_req.tp_frame_nr = c_buffer_nb;
/* calculate memory to mmap in the kernel */
size = s_packet_req.tp_block_size * s_packet_req.tp_block_nr;
/* set packet loss option */
tmp = mode_loss;
if (setsockopt(fd_socket, SOL_PACKET, PACKET_LOSS, (char *)&tmp, sizeof(tmp))<0)
{
perror("setsockopt: PACKET_LOSS");
return 1;
}
/* send TX ring request */
if (setsockopt(fd_socket, SOL_PACKET, PACKET_TX_RING, (char *)&s_packet_req, sizeof(s_packet_req))<0)
{
perror("setsockopt: PACKET_TX_RING");
return 1;
}
/* change send buffer size */
if(c_sndbuf_sz) {
printf("send buff size = %d\n", c_sndbuf_sz);
if (setsockopt(fd_socket, SOL_SOCKET, SO_SNDBUF, &c_sndbuf_sz, sizeof(c_sndbuf_sz))< 0)
{
perror("getsockopt: SO_SNDBUF");
return 1;
}
}
/* get data offset */
data_offset = TPACKET_HDRLEN - sizeof(struct sockaddr_ll);
/* mmap Tx ring buffers memory */
ps_header_start = mmap(0, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd_socket, 0);
if (ps_header_start == (void*)-1)
{
perror("mmap");
return 1;
}
while (1)
{
char * data;
char datagram[4096];
struct tpacket_hdr * ps_header;
int ec_send = 0;
printf("Iteration %d\n", i_index);
ps_header = ((struct tpacket_hdr *)((void *)ps_header_start) + (c_buffer_sz*i_index));
data = ((void*) ps_header) + data_offset;
memset (datagram, 0, 4096);
memcpy(data, datagram, 4096);
/* update packet len */
ps_header->tp_len = c_buffer_sz;
/* set header flag to USER (trigs xmit)*/
ps_header->tp_status = TP_STATUS_SEND_REQUEST;
//int ec_send;
static int total=0;
/* send all buffers with TP_STATUS_SEND_REQUEST */
/* Wait end of transfer */
//ec_send = sendto(fd_socket,NULL,0,MSG_DONTWAIT,(struct sockaddr *) ps_sockaddr,sizeof(struct sockaddr_ll));
ec_send = sendto(fd_socket,NULL,0,0,(struct sockaddr *) ps_sockaddr,sizeof(struct sockaddr_ll));
i_index++;
if (i_index >= c_buffer_nb) {
i_index = 0;
}
}
}
Note that the interface name is hard coded to enp3s0
. You will likely have to change it for the code to run on your system.
EDIT:
Here is the debugging information requested:
(gdb) p data
$1 = 0x7ffff7dbf020 <__GI_tolower> "\363\017", <incomplete sequence \372\215\227\200>
(gdb) p ps_header_start
$2 = (volatile struct tpacket_hdr *) 0x7ffff757f000
(gdb) p size
$3 = 8388608
(gdb)
Converting decimal 8388608
to hexadecimal gives 800000
. Adding 800000
to 7ffff757f000
gives 7FFFF7D7F000
. The data
pointer is indeed out of bounds.
The issue is here:
ps_header = ((struct tpacket_hdr *)((void *)ps_header_start) + (c_buffer_sz*i_index));
You're taking ps_header_start
, casting it to a struct tpacket_hdr *
and then adding a buffer size. But when you add to a pointer in C, the value added is multiplied by the size of the pointed-to type. In other words, instead of adding c_buffer_size * i_index
to the starting pointer, you're actually adding c_buffer_sz * i_index * sizeof(struct tpacket_hdr)
to it.
The right way to calculate the address of the next frame slot is:
ps_header = (struct tpacket_hdr *)((char *)ps_header_start + (c_buffer_sz * i_index));
Cast the starting pointer to a char *
(because the size of char
is one), then add to it the product of frame size and index, then cast it back to struct tpacket_hdr *
.