0

I searched a lot on SO, but only find these type of questions:

Consider the following code, where a bunch of variables are declared and initialized:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char **argv)
{
    char *src_ip_str, *dst_ip_str, *payload, *dp_str;
    u_int32_t src_ip = 0, dst_ip = 0;
    u_int16_t dp;
    if(argc != 5){
        //exit
    }  
    int l_src = strlen(argv[1]);
    int l_dst = strlen(argv[2]);
    int l_dp  = strlen(argv[3]);
    int l_pay = strlen(argv[4]);

    src_ip_str  = (char *)malloc(l_src * sizeof(char)+1);
    dst_ip_str  = (char *)malloc(l_dst * sizeof(char)+1);
    dp_str      = (char *)malloc(l_dp  * sizeof(char)+1);
    payload     = (char *)malloc(l_pay * sizeof(char)+1);

    strncpy(src_ip_str, argv[1], l_src); src_ip_str[l_src] = '\0';
    strncpy(dst_ip_str, argv[2], l_dst); src_ip_str[l_dst] = '\0';
    strncpy(dp_str    , argv[3], l_dp ); src_ip_str[l_dp ] = '\0';
    strncpy(payload   , argv[4], l_pay); //don't know if this should be null-terminated since it's TCP payload and don't know if it truncated the null-terminated string.
    dp = atoi(dp_str);
    // This is where I end GDB.

Some code was ommited, but this code clearly is acting strange. The program is invoked as such: sudo ./program <ip_src> <ip_dst> <port_dst> <payload_string>

Example: sudo ./program 1.1.19.70 172.16.1.255 513 some_payload

There is one bug and one potential bug:

  1. The src_ip_str gets truncated to 3 characters when dp_str is initialized with malloc. Before the initialization of dp_str it stays the full IP that is given (i.e. 1.1.19.70).
  2. l_dst, l_dp, l_pay do not give correct length values when examined in GDB with p .

I don't get this. When I inspect the memory of the arguments in strings and characters I get:

Strings:

(gdb) p argv[1]
$1 = 0xbffff86a "1.1.19.70"
(gdb) p argv[2]
$2 = 0xbffff875 "172.16.1.255"
(gdb) p argv[3]
$3 = 0xbffff882 "513"
(gdb) p argv[4]
$4 = 0xbffff886 "some_payload"

Characters (edit: I'm noticing that the characters come from an earlier debug session hence some values are off):

0xbffff86a:     49 '1'  57 '9'  46 '.'  48 '0'  46 '.'  49 '1'  57 '9'  46 '.'
0xbffff872:     56 '8'  57 '9'  0 '\000'        49 '1'  55 '7'  50 '2'  46 '.'  49 '1'
0xbffff87a:     54 '6'  46 '.'  49 '1'  46 '.'  50 '2'  53 '5'  53 '5'  0 '\000'
0xbffff882:     53 '5'  49 '1'  51 '3'  0 '\000'        115 's' 111 'o' 109 'm' 101 'e'
0xbffff88a:     95 '_'  112 'p' 97 'a'  121 'y' 108 'l' 111 'o' 97 'a'  100 'd'

Clearly these strings are null-terminated.

I really don't get why strlen(param) gives such strange values. Even when I cast it to size_t I get ridiculous values, e.g.:

(gdb) p (size_t)l_src
$12 = 10
(gdb) p (size_t)l_dst
$13 = 3086757876
(gdb) p (size_t)l_dp
$14 = 134516496
(gdb) p (size_t)l_pay
$15 = 3221223000

Does anyone have an idea what could be happening?

Compiler settings are:

gcc -ggdb -Wall `libnet-config --defines` `libnet-config --libs` program.c -o program

GDB settings are:

sudo gdb --args ./program 1.1.19.70 172.16.1.255 513 some_payload

Community
  • 1
  • 1
Melvin Roest
  • 1,392
  • 1
  • 15
  • 31
  • 2
    Ideally, you shouldn't cast to `size_t` -- `strlen` returns a `size_t`, so your variables should be declared as this type. This keeps your code portable. – SevenBits Feb 16 '16 at 23:59
  • Alright let me check that. – Melvin Roest Feb 17 '16 at 00:01
  • How do you check the values and how do you compile? Which optimisation settings do you use? Debugging optimised code tends to result in chaos. – too honest for this site Feb 17 '16 at 00:02
  • 1
    Also look into `strdup`. You're writing a lot of unnecessary code here. – Jonathon Reinhart Feb 17 '16 at 00:05
  • I casted to size_t, which unfortunately didn't work. Thanks for the tip though :) Portable code is important. – Melvin Roest Feb 17 '16 at 00:05
  • One suggestion would be to write your own version of `strlen`. Assuming you implement it right, if it gives the right value, then that gives valuable info. – SevenBits Feb 17 '16 at 00:08
  • I will look into strdup and implement my own version of strlen. – Melvin Roest Feb 17 '16 at 00:09
  • 1
    `strncpy` creates a null-*padded* string, not necessarily a null-*terminated* one. – dan04 Feb 17 '16 at 00:10
  • "Some code was omitted" Presumably some of the code you omitted was the code that you replaced by `//exit`, so that the program really *does* exit if `argc` isn't equal to 5. –  Feb 17 '16 at 00:11
  • +Guy Harris: yes and some variables that I use later on in the program that aren't relevant here. +dan04 You're right, I null-terminated all the strings that I thought that need it. The `payload` string isn't null-terminated but it's not showing bugs. `src_ip_str` is. – Melvin Roest Feb 17 '16 at 00:11
  • 2
    You repeated src_ip_str[l_src] = '\0' three times. Standard copy/paste bug :) – Hans Passant Feb 17 '16 at 00:18
  • Thanks for showing, I literally looked 90 minutes at this code. I'm so sorry for asking this question. The shame is on me. – Melvin Roest Feb 17 '16 at 00:20
  • 1
    Do strdup or strlen + malloc + strcpy. Why strNcpy when you've already measured the length of the string and allocated sufficiently large buffers. Live dangerous a little ;-) – Petr Skocik Feb 17 '16 at 00:21
  • Huh.. never thought about it that way. – Melvin Roest Feb 17 '16 at 00:22
  • `strncpy` is almost never what you want. It's somewhere on a line between dangerous and wasteful which never passes through good. – rici Feb 17 '16 at 00:29
  • 1
    Also: `p (size_t)l_pay` does not tell you what `strlen` returned. It tells you what `l_pay` contains after some buffer overflow overwrote it. (And it is obvious that something overwrote it, because the value shown is an address on your heap. Look at in in hex.) Suspecting `strlen` is buggy is distracting you from finding your bug. `strlen` is *not* buggy. – rici Feb 17 '16 at 00:36
  • That's a very good point and you're right. I wasn't fully aware that I thought that `strlen` was buggy, but I did. – Melvin Roest Feb 17 '16 at 00:39
  • Why do that strncpy-and-manually-null-terminate thing instead of just using strcpy? (And then why do that strlen-and-malloc-and-strcpy thing instead of just using strdup?) – user253751 Feb 17 '16 at 01:20
  • 1
    While not incorrect, these `malloc`s are rather amusing: `malloc(l_src * sizeof(char)+1)`. People often argue whether one should multiply by `sizeof(char)` in source code, since `sizeof(char)` is always guaranteed to be 1. This code seems to marry both viewpoints: the string length is multiplied by `sizeof(char)`, but that extra `+1` part (for terminating zero) is not :) – AnT stands with Russia Feb 17 '16 at 01:26

2 Answers2

1

In this code:

strncpy(src_ip_str, argv[1], l_src); src_ip_str[l_src] = '\0';
strncpy(dst_ip_str, argv[2], l_dst); src_ip_str[l_dst] = '\0';
strncpy(dp_str    , argv[3], l_dp ); src_ip_str[l_dp ] = '\0';

you never null-terminated dst_ip_str and dp_str.

This kind of bug is why many people consider strncpy a less-safe version of strcpy, and we avoid it at all costs. This part of your code could have been written in a simpler and less error-prone fashion:

strcpy(src_ip_str, argv[1]);
strcpy(dst_ip_str, argv[2]);
strcpy(dp_str,     argv[3]);

You may as well strcpy payload as well, since it is coming into you as a null-terminated string.

You may use the non-ISO C function strdup to replace the combination of malloc and strcpy. (This is only guaranteed to exist on POSIX, but if porting to a system that doesn't have it, it's trivial to roll your own).

In either case it would be a good idea to check for allocation failure if you are interested in writing robust code.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • thought strcpy() more prone to buffer overflows and the like. Isn't it? Edit: never mind, not with command line arguments since they are null-terminated. I assume that strcpy() copies that as well. I'd have to look it up. – Melvin Roest Feb 17 '16 at 00:32
  • @MelvinRoest: Not if you use `strdup`. (And "`strdup` doesn't exist on platform X" is not an excuse. You can write it safely and correctly in two lines, and those two lines are readily available.) – rici Feb 17 '16 at 00:34
  • 1
    @MelvinRoest Not really. Your code is a good example: you introduced a buffer overflow (reading off the end of `dst_ip_str` and `dp_str`), whereas if you had used `strcpy` then there is no overflow. – M.M Feb 17 '16 at 00:35
  • 1
    @rici have edited in line with your comment – M.M Feb 17 '16 at 00:36
  • @M.M: cool. I can't upvote again, though. (And where on earth did those downvotes come from? Sometimes I don't understand the logic of SO responders.) – rici Feb 17 '16 at 00:38
  • I guess the strncpy fan brigade is out today :) – M.M Feb 17 '16 at 00:38
1

Credits go to +Hans Passant, his name should be under this answer (don't know how to make it happen, if he posts an answer I'll delete this one). It's a classic version of looking long at one's code while the answer is staring you in the face.

The error is here:

strncpy(src_ip_str, argv[1], l_src); src_ip_str[l_src] = '\0';
strncpy(dst_ip_str, argv[2], l_dst); src_ip_str[l_dst] = '\0';
strncpy(dp_str    , argv[3], l_dp ); src_ip_str[l_dp ] = '\0';

I null-terminated src_ip_str[x] three times! I feel shame, nevertheless I do feel compelled to answer what happens exactly.

Consider the source IP: 1.1.19.70 (GNU standard time ;) ) and destination IP: 172.16.1.255.

  1. The first time you have: 1.1.19.70\0
  2. The second time you have: 1.1.19.70\0x\0 (the destination IP is longer)
  3. The third time you have: 1.1\019.70\0x\0

I should use strdup() or write less code to make less errors with null-terminated strings.

Thank you all for helping! :)

Melvin Roest
  • 1,392
  • 1
  • 15
  • 31
  • 1
    Well, the real answer is: you used `strncpy` for something it has never been intended to be used for. And paid the price for trying to force that square peg into a round hole. If you are on Linux platform, you should have used `strlcpy` to do limited-length zero-terminated string copying. – AnT stands with Russia Feb 17 '16 at 01:23