Tuesday, November 25, 2014

Order of Events

inet_ntoa uses static storage for the result it returns. The GNU implementation of inet_ntoa uses the following internally:

static __thread char buffer[18];

This makes the function thread-safe but this safety does not remove the need to worry about use within a single thread. Consider the following snippet of code:

#include <arpa/inet.h>
#include <stdio.h>
int main () {
    struct in_addr a = { .s_addr = 1234567 }, b = { .s_addr = 7654321 };
    return printf ("%s : %s\n", inet_ntoa (a), inet_ntoa (b));
}

Since inet_ntoa is used twice within the argument list the result is dependent on the order of evaluation of the arguments to printf. Regardless of which call gets evaluated first the output will always print the same IP address twice. On my system, the result is:

135.214.18.0 : 135.214.18.0

This is a result of two things: arguments are evaluated before their results are used by printf; and inet_ntoa overwrites static storage on each invocation. Looking at the instructions for this C code makes this clear:

.LC0:                                                                                                                                                                       
        .string "%s : %s\n"
        .text
        .globl  main
        .type   main, @function
main:
        pushl   %ebp
        movl    %esp, %ebp
        pushl   %ebx
        andl    $-16, %esp
        subl    $32, %esp
        movl    $1234567, 24(%esp)
        movl    $7654321, 28(%esp)
        movl    28(%esp), %eax
        movl    %eax, (%esp)
        call    inet_ntoa
        movl    %eax, %ebx        ; pointer stored to static memory
        movl    24(%esp), %eax
        movl    %eax, (%esp)
        call    inet_ntoa
        movl    $.LC0, %edx
        movl    %ebx, 8(%esp)     ; arg2 to printf; pointer from above
        movl    %eax, 4(%esp)     ; arg1 to printf; new pointer, same static memory
        movl    %edx, (%esp)      ; arg0 (format string)
        call    printf
         movl    -4(%ebp), %ebx
        leave
        ret

The correct way to call inet_ntoa consecutively is to save each result to a local variable.

#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
int main () {
    struct in_addr a = { .s_addr = 1234567 }, b = { .s_addr = 7654321 };
    char ipa[18] = { 0 }, ipb[18] = { 0 };
    strcpy (ipa, inet_ntoa (a));
    strcpy (ipb, inet_ntoa (b));
    return printf ("%s : %s\n", ipa, ipb);
}