0

I am trying to write a packet decoder for the SCTP protocol in C and running into some issues while dereferencing a pointer to a union of structs (which represent the SCTP chunks).

Not everyone might be familiar with the SCTP (Stream Control Transmission Protocol), so here are some brief primers:

Stream Control Transmission Protocol
SCTP packet structure
RFC 4960

In a nut shell, after the SCTP Common header are a series of one or more "chunks". Most of the time, each SCTP packet has just one chunk type, but you can have chunk bundling in which certain chunk types can be bundled together into a single packet. Because of this bundling, I can't just define a union inside my SCTP header structure and call it a day.

So this is what I have for 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;
};


And for union sctp_chunks (truncated to just two chunk types for this question):

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



Right now, I am overlaying sctp_chunks onto sctp_header->chunks (once I have done all the other necessary checks to make sure I am sitting on an SCTP packet). I then read sctp_header->ctype in a switch statement, and based on that, I know whether I can access sctp_header->chunks->data->tsn or sctp_header->chunks->init->initate_tag (after casting to (sctp_chunks *)), and so on for other chunk types. Later on, I'll do math and check for remaining chunks, and re-overlay the sctp_chunks union onto the remaining data until I have processed all chunks. For now, I am operating on just the first chunk.

Problem is, trying to access data->tsn or init->initiate_tag (or any other member of the union) causes a SIGSEGV. I don't have GDB handy (I am just a user on the machine this is being coded on), so I am having a hard time figuring out why my program is segfaulting. I believe my use of the structures/unions to be sound, but thus is the nature of C, it's probably something REALLY subtle that is hooking me here.

Printing the pointer addresses for chunks->data or chunks->init shows me addresses that do not appear to be NULL pointers, and I am not getting any major errors or warnings out of gcc, so I am a bit stumped.

Anything out of the ordinary, or is there a better way to tackle this?

Community
  • 1
  • 1
Kumba
  • 2,390
  • 3
  • 33
  • 60
  • You can print short memory dump instead of just pointer values and check if the contents make sense. Are structures properly aligned (#pragma pack etc.)? – hamstergene Aug 16 '11 at 03:46
  • If no `gdb` than do it the dirty way i.e. a lot of `printf`s. – hari Aug 16 '11 at 03:47
  • @Eugene: Have not tried #pragma pack, but I made some modifications (moved chunk type, length, flags into sctp_chunks) and can dereference those members once I stopped making them pointers. But now I am offset by four bytes, which is **weird**. – Kumba Aug 16 '11 at 03:54

2 Answers2

3

When you say you are "overlaying" your data-structure on the packet (at least that's what it seems you're saying), the value that appears in the four or eight bytes of your void* pointer data-member (depending on your platform) is most likely a value inside either asctp_init or scpt_data chunk, not an actual pointer to that chunk of data. That is most likely why your pointer is not NULL, yet your seg-faulting when dereferencing the value in the pointer.

Secondly, overlaying a structure over serialized data without pragmas and/or compiler directives on the packing of the struture can be dangerous ... sometimes how you think the compiler may allocate/pad the structure is not how it actually ends up doing it, and then the structure does not match the actual format of the data-packet, and you'll end up with invalid values.

So I'm assuming right now you're trying to-do something like this by directly overlaying the first N bytes of your packet with the scpt_header structure:

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

That's not very portable, and can cause a lot of headaches especially when you insert network-byte-order issues into the situation. What you really want is something where you copy the contents of your SCPT packet into an internal data-structure, like a linked-list, that you can then properly manipulate.

One way could look like the following:

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

This approach may seem cumbersome, but unfortunately if you're going to be dealing with endian and network-byte-order issues, you're not going to be able to simply overlay a data-structure on the packet data in a platform-portable manner, even if you declare it as a packed data-structure. Declaring a packed data-structure will align the bytes properly, but it won't correct endian issues which is what functions like ntohs and ntohl will do.

Also don't let your scpt_header go out-of-scope without destroying the linked list it "owns" or else you're going to end up with a memory leak.

Update: If you still want to go the overlaying route, first make sure you are using a compiler directive to pack your struct so that there is no padding added. In gcc this would be

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;

but would be something else in other compilers. Also please note that I've changed your structures around a bit to better reflect how they are actually represented by the SCTP packet in-memory. Because the size of the two different packet types is different, we can't really do a union and overlay that in memory ... the union will technically be the size of the largest chunk-type member, and that's going to create problems when we try to create arrays, etc. I'm also getting rid of the pointers ... I see what you're trying to-do with them, but again, since you want to overlay these data-structures on the packet data, that's again going to cause problems since you're actually trying to "shift" the data around. The modifications to your original data-structures has been made to actually mirror how the data is laid out in memory without any fancy shifting or pointer casting. Since the type of each packet is represented by an unsigned char, we can now do the following:

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

Keep in mind again that this method only corrects for alignment issues ... it does not correct for endianness, so beware as you read any values in a multi-byte data-type.

Jason
  • 31,834
  • 7
  • 59
  • 78
  • I think you're onto something. I followed Eugene's idea to look into #pragma pack and am now reading bits at the right offsets. But I am running into the bloody endian issues again (I am becoming a proponent of big endian....). Still, this is something new for me. I had no problems dealing with this when I wrote a kernel driver. But I think there, I was overlaying a structure onto a buffer that was not serialized (i.e., some kernel function already isolated the data) and things worked because I started at the first chunk of data. – Kumba Aug 16 '11 at 04:04
  • Can you provide a small code sample to demonstrate copying to a buffer and then overlaying onto that? That might line up better with what I dealt with in the kernel driver. – Kumba Aug 16 '11 at 04:05
  • This does look cumbersome. I know I've seen other cases of where data was copied out of a `void *` into a new buffer (and malloc'ed), then a structure overlayed and its members access (though via `->` or `.`, I forget). I've done tricks similar in other languages, but those, I had a good debugger handy so I could see what I was doing. Endianness shouldn't really be a concern unless I get into bitfields, then I'll just use #ifdef and define specific LE & BE versions. `ntohs` and `ntohl` can take care of specific variables once I have my structure aligned properly. – Kumba Aug 16 '11 at 04:47
  • PS, I am reading packets only. Not writing modified or new ones back out. – Kumba Aug 16 '11 at 04:49
  • Okay, I've given you an update in the answer that address just overlaying your structures on the packet data. – Jason Aug 16 '11 at 13:04
  • Minor notes, there is an SCTP Common Header that occupies 12 bytes at the beginning of each SCTP Packet. After the common header can be 1 or more chunk types. Most commonly 1, but I actually have test PCAP data where I have two chunks bundled together. My thinking to approach this is to read in the SCTP common header by "overlaying" that structure onto the packet data (which should already be a pointer _past_ the IP header and other bits). Then, increment the temp pointer by 12 bytes and I should be on the first chunk. – Kumba Aug 17 '11 at 03:22
  • This is where the unions came into play. All chunk types have three fields in common: chunk type, flags, and length (byte, byte, int16). I figured using a struct with those three members, then a union to represent the different chunk data was the best approach, since I can 'switch' on the chunk type and make sure to only execute code matching the correct chunk. – Kumba Aug 17 '11 at 03:24
  • I think, if I am reading your code block right, the new idea would be to overlay a `sctp_chunk_header`, read that in, advance the temp pointer, then based on the chunk type, overlay the correct struct. This would save having to replicate the three common fields on every chunk type. Arguably, I'd like to use unions. I am looking at some other programs that do packet decoding (like Wireshark and Snort), and Snort uses a union for decoding ICMP by overlaying different structs and reading the appropriate data after it deals with the common ICMP header. – Kumba Aug 17 '11 at 03:27
  • Your last comment interprets what I'm suggesting correctly. If you want, you *could* overlay a union-type rather than an explicit structure-type like I've done, but the union would be a union of the two data-structures as I've declared them, not your original ones with the pointers at the end, and no chunk-header information at the beginning. The thing with unions though is I'm not quite sure how the compiler-directive for packing the structures will work. Hopefully they will still remain properly packed, but I've never done it that way, so you're going to have to experiment with that. – Jason Aug 17 '11 at 03:33
  • The `void *` pointers at the end of those two is a place holder. That's where the chunk-specific variable length data/parameters would be found. Those will require further structs to be defined (or unions). SCTP is nice because it uses TLV format, so it's just walking the chunks correctly. I will probably replicate the same approach (once I get it to compile) to read in the parameters as I did for the chunks. – Kumba Aug 17 '11 at 03:57
  • I see what you're trying to-do with the `void*`, but you do realize that for anyone else reading your code, they will think that's an actual pointer. Having to-do a bunch of data mangling is just causing anyone else who might have to use/maintain your code to go through a lot of confusing steps, and the `void*` does not model the actual underlying data representation of the chunk. When I look at the representation of a chunk from the Wikipedia article, I see a header to the chunk listing it's length, etc., and I see a data-payload. I don't see any `void*`, and so it just creates confusion. – Jason Aug 17 '11 at 11:49
  • Yeah, I got rid of them already. Using a temp pointer from your example, and assigning the struct to it , then advancing it the length of the header (or chunk size) works so far. Even the unions work correctly thus far. Haven't made any further progress the last few days due to other priorities. – Kumba Aug 19 '11 at 05:59
  • Works so far. Thanks! I think I can work from what I have and reference the rest of your answer if I encounter any more problems. – Kumba Aug 23 '11 at 03:45
1

The problem is that you shouldn't be using pointers at all. sctp_chunks should be:

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

And chunks in sctp_header should be union sctp_chunks chunks[1] (you can safely index chunks beyond 1 if you know the data will be valid.

I just saw Jason's answer, and he's right about structure packing. If you're using gcc, define the structure like:

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

You'll have to do that for each structure.

Klox
  • 931
  • 12
  • 21
  • Is `__attribute__` a GCC-specific extension? I don't have access to MSVC++ to check and see if it understands that directive. – Kumba Aug 16 '11 at 04:16
  • Also, why `chunks[1]`? I thought arrays in C were zero-indexed? – Kumba Aug 16 '11 at 04:18
  • Scratch the first comment. `__attribute__` is GCC specific, per [this question](http://stackoverflow.com/q/1537964/482691). I guess it is safer to go with `#pragma pack`, as that looks more portable. – Kumba Aug 16 '11 at 04:22
  • I was going to say gcc doesn't use `#pragma` (because in our code we treat gcc and MSVC++ differently), but a quick Google shows that gcc honors the MSVC++ `#pragma`. – Klox Aug 17 '11 at 01:41
  • Arrays are zero indexed, but in this case the [1] is a count. I'm pretty sure gcc would allow [0] for a size (for just a such a situation where you have an unknown number of chunks), but I used [1] for compatibility with other compilers. The number doesn't matter unless you're using sizeof() or allocating memory for the structure. I assumed that you had a byte pointer that you were casting as the structure, which is why you didn't know how many chunks you will have. – Klox Aug 17 '11 at 01:45