cstringsegmentation-faultstrtokstrsep

Chained strsep gives segmentation fault - C


I'm trying to create an array of array of strings to prepare them to be shown in a table.

So I have a function that returns a buffer string with the list of some scanned wifi access points, and I'm using strsep to split it by "\n" and then again by "\t".

The loop runs fine until it reaches the end and when the while argument ((line = strsep(&buf, "\n"))) is evaluated it gives a SEGFAULT.

Short Illustrative example asked per @Jabberwocky:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static int
wap_scan_count_lines(char*      wap_scan)
{
    int   line_amount = 0;
    char *scan = wap_scan;

    while(*scan)
    {
        if ('\n' == *scan){
            line_amount++;
        }
        scan++;
    }
    return line_amount;
}

int main() {

    char ***scan_result, *line=NULL, *item=NULL, *scan=NULL;
    scan = strdup("bssid / frequency / signal level / flags / ssid\n"
                  "a8:6a:bb:e2:d6:ef       5785    -47     [WPA-PSK-CCMP+TKIP][WPA2-PSK-CCMP+TKIP][WPS][ESS]       Fibertel WiFi114 5.8GHz");
    int wap_scan_size = wap_scan_count_lines(scan);
    scan_result = malloc(wap_scan_size * sizeof(**scan_result));
    int i = 0;
    int item_len = sizeof (*scan_result);

    while((line = strsep(&scan, "\n")) != NULL ) {
        if(i==0){
            i++;
            continue;
        }
        char **scan_line = calloc(5, item_len);
        int j = 0;
        while ((item = strsep(&line, "\t")) != NULL) {
            printf("%s\n", item);
            scan_line[j++] = strdup(item);
        }
        scan_result[i++] = scan_line;
    }
    return 0;
}

The real function that gives me the problem:

char *** wifi_client_get_wap_list(int *len)
{
    char ***scan_result;
    char *buf, *buf_free, *cmd, *line, *item;
    int ret, items_len;
    cmd = strdup("SCAN");
    ret = wpa_ctrl_command(cmd, NULL);
    if (ret < 0) goto error;

    cmd = strdup("SCAN_RESULTS");
    ret = wpa_ctrl_command(cmd, &buf); //RETURNS A STRING ON BUF ALLOCATED BY STRDUP
    if (ret < 0){
        free(buf);
        goto error;
    }

    *len = wap_scan_count_lines(buf); //NUMBER OF LINES IN THE SCAN RESULT
    scan_result = calloc(*len, sizeof(**scan_result));
    int i = 0, j;
    buf_free = buf;
    items_len = sizeof (*scan_result);

    while ((line = strsep(&buf, "\n"))){ //THIS GIVES THE SEGFAULT AT THE END
        // SKIP FIRST LINE WITH HEADERS
        if (i==0){
            i++;
            continue;
        }

        //if (strcmp(line, "") == 0) {
        //  break;   
        //}

       //EACH LINE HAS 5 VALUES (bssid, freq, level,flags,ssid)
        char **scan_line = calloc(5, items_len); 
        j = 0;
        printf("INNER STEPS:\n");
        while((item = strsep(&line, "\t"))){
            *(scan_line + j) = strdup(item);
            printf("%d ", j);
            j++;
        }
        *(scan_result + i) = scan_line;
        printf("\nSTEP: %d\n", i);
        i++;
    }

    free(buf_free);
    free(cmd);
    return scan_result;

    error:
    // @TODO: Handle error
    if (ret == -2) {
        printf("'%s' command timed out.\n", cmd);
    } else if (ret < 0) {
        printf("'%s' command failed.\n", cmd);
    }

    free(cmd);
    return NULL;
}

Solution

  • Based on https://man7.org/linux/man-pages/man3/strsep.3.html the issue is that the loop will run one more time than you want it to, causing scan_result to overflow.

    The relevant parts of the documentation are:

       The strsep() function returns a pointer to the token, that is, it
       returns the original value of *stringp.
    

    and

       If *stringp is NULL, the strsep() function returns NULL and does
       nothing else.  Otherwise, this function finds the first token in
       the string *stringp, that is delimited by one of the bytes in the
       string delim.  This token is terminated by overwriting the
       delimiter with a null byte ('\0'), and *stringp is updated to
       point past the token.  In case no delimiter was found, the token
       is taken to be the entire string *stringp, and *stringp is made
       NULL.
    

    In wap_scan_count_lines you count the number of lines that are terminated with '\n'.

    In the following 2 lines, you allocate the memory to hold the result based on the number of lines terminated with '\n'.

    int wap_scan_size = wap_scan_count_lines(scan);
    scan_result = malloc(wap_scan_size * sizeof(**scan_result));
    

    However, the above quoted documentation for strsep() implies that in your simplified example the first wap_scan_size times strsep is called, at the end of the call the result will not be NULL and scan won't be set to NULL during the call. The next time through the call, scan will be set to NULL during the call but the result will not be NULL. This means that the body of the loop will be executed wap_scan_size + 1 times, causing a write past the end of scan_result.

    There are at least two possible fixes, depending on whether you actually want to process any line at the end of the input that is not terminated by '\n'.

    If you do need to process such lines, which seems more robust to me, particularly given that your simplified example ends with such a line, just allocate one extra entry in scan_result:

    scan_result = malloc((wap_scan_size + 1) * sizeof(**scan_result));
    

    If you are quite sure that you do not need to process such lines, but this seems incorrect to me, change:

    while((line = strsep(&scan, "\n")) != NULL ) {
    

    to

    for(line = strsep(&scan, "\n"); scan != NULL; line = strsep(&scan, "\n") ) {