0

I've been testing out interactions between malloc() and various string functions in order to try to learn more about how pointers and memory work in C, but I'm a bit confused about the following interactions.

char *myString = malloc(5); // enough space for 5 characters (no '\0')
strcpy(myString, "Hello"); // shouldn't work since there isn't enough heap memory
printf(%s, %zd\n", myString, strlen(myString)); // also shouldn't work without '\0'
free(myString);

Everything above appears to work properly. I've tried using printf() for each character to see if the null terminator is present, but '\0' appears to just print as a blank space anyways.

My confusion lies in:

  • String literals will always have an implicit null terminator.
  • strcpy should copy over the null terminator onto myString, but there isn't enough allocated heap memory
  • printf/strlen shouldn't work unless myString has a terminator

Since myString apparently has a null terminator, where is it? Did it just get placed at a random memory location? Is the above code an error waiting to happen?

Prism
  • 53
  • 1
  • 4
  • 4
    Buffer overrun. – jxh Dec 12 '17 at 23:30
  • 8
    Repeat after me: "Undefined behavior" means "undefined". It doesn't mean "guaranteed to fail", "will produce warnings", or anything else predictable. Just because it works, that doesn't mean there's nothing wrong. – Lee Daniel Crocker Dec 12 '17 at 23:36
  • 5
    you're invoking undefined behavior by overrunning your buffer. You're just getting lucky that "every above appears to work properly". Really I should say _un_lucky, because you hope your program crashes when there are problems like this so you can fix them. Otherwise, they manifest at the most inopportune of times... – yano Dec 12 '17 at 23:37
  • Oh I see. I guess I didn't understand malloc() well enough. So basically this works by chance, but there's also the possibility of overwriting other memory locations or segmentation faults? – Prism Dec 12 '17 at 23:37
  • 1
    you are overwriting memory you don't own when you overflow a buffer. Whether or not that memory is within your process space, or critical to continued functionality of the program, that's the "who knows?" part. In your case, _this time_, it wasn't critical. Maybe that will change in 5 min, or when you run it on your friend's machine, or on your other friend's machine with a different OS, or when you compile it with different compile options. – yano Dec 12 '17 at 23:41
  • 1
    If you are using `gcc`, try adding `-fsanitize=address`. – jxh Dec 12 '17 at 23:41
  • 1
    `malloc` also isn't hamstrung into giving you _exactly_ how many bytes you ask for. You asked for 5, maybe it gave you 16 instead. In fact I think some `malloc` implementations allocate more memory than you ask for for bookkeeping, but I don't know much about that. Bottomline, if you asked `malloc` for 5 bytes and it doesn't return NULL, then you can only depend on having 5 usable bytes. – yano Dec 12 '17 at 23:43
  • 1
    `strcpy` does not know how large the buffer you allocated is and will overrun the buffer. You are only giving it a pointer. A `char*` pointer is a pointer to a single `char`. I suggest you look at [`strncpy`](https://linux.die.net/man/3/strncpy). – Galen Dec 12 '17 at 23:47
  • 1
    @yano `malloc` implementations I've examined return memory on either 8 or 16-byte addresses no matter how many bytes you request because the memory must be "suitable for any purpose". You are likely getting memory in 8- or 16-byte chunks internally to the implementation. So overrunning a 5-byte buffer by 2 bytes can "work" (and even if it "works", it won't if someone changes upstream code and the buffer grows to 16 bytes and still gets overrun by two...) – Andrew Henle Dec 13 '17 at 00:18
  • @AndrewHenle good info, thanks! – yano Dec 13 '17 at 01:01

1 Answers1

4

Addressing your three points:

  • String literals will always have an implicit null terminator.

Correct.

  • strcpy should copy over the null terminator onto myString, but there isn't enough allocated heap memory

strcpy has no way of knowing how large the destination buffer is, and will happily write past the end of it (overwritting whatever is after the buffer in memory. For information on this off-the-end-access look up 'buffer overrun' or 'buffer overflow'. These are common security weaknesses). For a safer version, use strncpy which takes the length of the destination buffer as an argument so as not to write past the end of it.

  • printf/strlen shouldn't work unless myString has a terminator

The phrase 'shouldn't work' is a bit vague here. printf/strlen/etc will continue reading through memory until a null terminator is found, which could be immediately after the string or could be thousands of bytes away (in your case you have written the null terminator to the memory immediately after myString so printf/strlen/etc will stop there).

Lastly:

  • Is the above code an error waiting to happen?

Yes. You are overwriting memory that has not been allocated which could cause any manor of problems depending on what happened to be overwritten. From the strcpy man page:

If the destination string of a strcpy() is not large enough, then anything might happen. Overflowing fixed-length string buffers is a favorite cracker technique for taking complete control of the machine. Any time a program reads or copies data into a buffer, the program first needs to check that there's enough space. This may be unnecessary if you can show that overflow is impossible, but be careful: programs can get changed over time, in ways that may make the impossible possible.

MC-Squared
  • 88
  • 1
  • 4
  • Note that while `strncpy()` won't write beyond the limit specified, the downside is that the copied data isn't necessarily a string; it might have no null byte at the end. – Jonathan Leffler Dec 13 '17 at 10:03