Безопасно разыменование указателей на объединения структур в C? - PullRequest
0 голосов
/ 16 августа 2011

Я пытаюсь написать декодер пакетов для протокола SCTP на C и сталкиваюсь с некоторыми проблемами при разыменовании указателя на объединение структур (которые представляют блоки SCTP).

Не все могут быть знакомыс SCTP (протокол управления потоком данных), поэтому вот несколько кратких учебников:

Протокол передачи управления потоком
структура пакета SCTP
RFC 4960

В скорлупе гайки после общего заголовка SCTP - это серия из одного или нескольких «кусков».В большинстве случаев каждый SCTP-пакет имеет только один тип чанка, но у вас может быть чанкинг, в котором определенные типы чанков могут быть объединены в один пакет.Из-за этого связывания я не могу просто определить объединение внутри моей структуры заголовка SCTP и назвать его днем.

Так вот что у меня есть для struct sctp_header:

struct sctp_header {
    uint16_t srcport;
    uint16_t dstport;
    uint32_t vtag;
    uint32_t chksum;
    uint8_t ctype;
    uint8_t cflags;
    uint16_t clength;
    void *chunks;
};


И для union sctp_chunks (урезано до двух типов фрагментов для этого вопроса):

struct sctp_data {
    uint32_t tsn;
    uint16_t stream_id;
    uint16_t stream_seq;
    uint32_t pp_id;
    void *data;
};

struct sctp_init {
    uint32_t initate_tag;
    uint32_t a_rwnd;
    uint16_t num_out_streams;
    uint16_t num_in_streams;
    uint32_t tsn;
    void *params;
};

union sctp_chunks {
    struct sctp_data *data;
    struct sctp_init *init;
};



Сейчас я накладываю sctp_chunks на sctp_header->chunks (один разЯ сделал все другие необходимые проверки, чтобы убедиться, что я сижу на пакете SCTP).Затем я читаю sctp_header->ctype в операторе switch и, основываясь на этом, я знаю, могу ли я получить доступ к sctp_header->chunks->data->tsn или sctp_header->chunks->init->initate_tag (после приведения к (sctp_chunks *)) и т. Д. Для других типов чанков.Позже я выполню математику и проверю наличие оставшихся кусков, а затем снова наложу объединение sctp_chunks на оставшиеся данные, пока не обработаю все куски.Пока я работаю только с первым чанком.

Проблема в том, что попытка доступа к data->tsn или init->initiate_tag (или любому другому члену объединения) вызывает SIGSEGV.У меня нет под рукой GDB (я всего лишь пользователь на машине, на которой это кодируется), поэтому мне трудно понять, почему моя программа является segfaulting.Я полагаю, что мое использование структур / союзов было правильным, но, таким образом, это природа C, возможно, это что-то ДЕЙСТВИТЕЛЬНО тонкое, что меня цепляет.

Печать адресов указателей для chunks->data или chunks->initпоказывает мне адреса, которые не являются указателями NULL, и я не получаю каких-либо серьезных ошибок или предупреждений из gcc, поэтому я немного озадачен.

Что-то необычное, или есть лучшеспособ справиться с этим?

Ответы [ 2 ]

3 голосов
/ 16 августа 2011

Когда вы говорите, что «накладываете» свою структуру данных на пакет (по крайней мере, это то, о чем вы говорите), значение, которое появляется в четырех или восьми байтах вашего void* указателя на элемент данных указателя(в зависимости от вашей платформы), скорее всего, это значение внутри блока sctp_init или scpt_data, а не фактический указатель на этот блок данных.По всей вероятности, ваш указатель не равен NULL, но при использовании разыменования значения в указателе происходит сбой сегмента.

Во-вторых, наложение структуры на сериализованные данные без прагм и / или директив компилятора при упаковкеstruture может быть опасным ... иногда вы думаете, что компилятор может распределить / дополнить структуру не так, как он фактически делает это, и тогда структура не соответствует фактическому формату пакета данных, и вы закончитес недопустимыми значениями.

Итак, я предполагаю, что сейчас вы пытаетесь сделать что-то подобное, накладывая первые N байтов вашего пакета структурой scpt_header:

|scpt_header .........|void*|
|packet information ..|scpt_chunk.................|

Это не очень переносимо и может вызвать много головных болей, особенно когда вы вставляете в ситуацию проблемы с порядком следования байтов в сети.Что вам действительно нужно, так это то, что вы копируете содержимое своего пакета SCPT во внутреннюю структуру данных, например, связанный список, которым вы можете затем правильно манипулировать.

Один из способов может выглядеть следующим образом:

#include <arpa/inet.h>
unsigned char* packet_buffer = malloc(sizeof(PACKET_SIZE));

//... proceed to copy into the buffer however you are reading your packet

//now fill in your structure
unsigned char* temp = packet_buffer;
struct sctp_header header_data;

header_data.src_port = ntohs(*((uint16_t*)temp));
temp += sizeof(uint16_t);
header_data.dstport = ntohs(*((uint16_t*)temp));
temp += sizeof(uint16_t);
header_data.vtag = ntohl(*((uint32_t*)temp));
temp += sizeof(uint32_t);
//... keep going for all data members

//allocate memory for the first chunk (we'll assume you checked and it's a data chunk)
header_data.chunks = malloc(sizeof(sctp_data));
scpt_data* temp_chunk_ptr = header_data.chunks;

//copy the rest of the packet chunks into your linked-list data-structure
while (temp < (packet_buffer + PACKET_SIZE))
{
    temp_chunk_ptr->tsn = ntohl(*((uint32_t*)temp));
    temp += sizeof(uint32_t);
    //... keep going for the rest of this data chunk

    //allocate memory in your linked list for the next data-chunk
    temp_chunk_ptr->data = malloc(sizeof(scpt_data));
    temp_chunk_ptr = temp_chunk_ptr->data;
}

//free the packet buffer when you're done since you now have copied the data into a linked
//list data-structure in memory
free(packet_buffer);

Этот подход может показаться громоздким, но, к сожалению, если вы собираетесь решать проблемы с порядком байтов и порядком байтов, вы не сможете просто наложить структуру данных напакетные данные переносимым на платформу способом, даже если вы объявляете их как упакованную структуру данных.Объявление упакованной структуры данных приведет к правильному выравниванию байтов, но не устранит проблемы с порядком байтов, что будут делать функции, такие как ntohs и ntohl.

Также не позволяйте вашему scpt_headerвыйдите из области видимости, не разрушая связанный список, которым он «владеет», иначе вы получите утечку памяти.

Обновление : Если вы все еще хотите перейтиналожив маршрут, сначала убедитесь, что вы используете директиву компилятора, чтобы упаковать свою структуру, чтобы не было добавлено дополнение.В gcc это будет

typdef struct sctp_header {
    uint16_t srcport;
    uint16_t dstport;
    uint32_t vtag;
    uint32_t chksum;
} __attribute__((packed)) sctp_header;

typedef struct sctp_data 
{
    uint8_t ctype;
    uint8_t cflags;
    uint16_t clength;
    uint32_t tsn;
    uint16_t stream_id;
    uint16_t stream_seq;
    uint32_t pp_id;
} __attribute__((packed)) sctp_data;

typedef struct sctp_init 
{
    uint8_t ctype;
    uint8_t cflags;
    uint16_t clength;
    uint32_t initate_tag;
    uint32_t a_rwnd;
    uint16_t num_out_streams;
    uint16_t num_in_streams;
    uint32_t tsn;
} __attribute__((packed)) sctp_init;

, но это будет нечто другое в других компиляторах.Также обратите внимание, что я немного изменил ваши структуры, чтобы лучше отразить, как они на самом деле представлены пакетом SCTP в памяти.Поскольку размер двух разных типов пакетов различен, мы не можем действительно выполнить объединение и наложить это в памяти ... объединение будет технически размером с самый большой член типа чанка, и это будет создавать проблемы, когдамы пытаемся создать массивы и т. д. Я также избавляюсь от указателей ... Я вижу, что вы пытаетесь с ними делать, но опять же, поскольку вы хотите наложить эти структуры данных на пакетные данные,это снова вызовет проблемы, так как вы на самом деле пытаетесь «сдвинуть» данные.Изменения в ваших исходных структурах данных были сделаны для того, чтобы фактически отражать, как данные размещаются в памяти без какого-либо необычного смещения или приведения указателя.Поскольку тип каждого пакета представлен unsigned char, теперь мы можем сделать следующее:

enum chunk_type { DATA = 0, INIT = 1 };

unsigned char* packet_buffer = malloc(sizeof(PACKET_SIZE));
//... copy the packet into your buffer

unsigned char* temp = packet_buffer;

sctp_header* header_ptr = temp;
temp += sizeof(sctp_header);

//... do something with your header

//now read the rest of the packets
while (temp < (packet_buffer + PACKET_SIZE))
{
    switch(*temp)
    {
        case DATA:
            sctp_data* data_ptr = temp;
            //... do something with the data
            temp += data_ptr->clength;
            break;

        case INIT:
            sctp_init* init_ptr = temp;
            // ... do something with your init type
            temp += init_ptr->clength;
            break;

        default:
            //do some error correction here
    }
}

Имейте в виду, что этот метод исправляет только проблемы с выравниванием ... он не исправляет дляпорядковый номер, поэтому будьте осторожны, когда вы читаете любые значения в многобайтовом типе данных.

1 голос
/ 16 августа 2011

Проблема в том, что вы вообще не должны использовать указатели. sctp_chunks должно быть:

union sctp_chunks {
    struct sctp_data data;
    struct sctp_init init;
};

И chunks в sctp_header должно быть union sctp_chunks chunks[1] (вы можете безопасно индексировать порции больше 1, если знаете, что данные будут действительными.

Я только что видел ответ Джейсона, и он прав насчет упаковки конструкций. Если вы используете gcc, определите структуру следующим образом:

struct sctp_header __attribute__ ((packed)) {
  ...
};

Вы должны будете сделать это для каждой структуры.

...