2

tl;dr

Why do I get different output every time I run this code (Ideone):

#include <iostream>
#include <stdio.h>
using namespace std;

int main() {

    const char* _user = "FOO";
    const char* _password = "BAR";

    char login[21];
    sprintf(login,
        "\x15\x00\x01%-8s%-10s",
        _user,
        _password);

    for (int i = 0; i < 21; i++) {
        printf(" %02x", login[i] & 0xff);
    }

    return 0;
}

But not this code (Ideone):

#include <iostream>
#include <stdio.h>
using namespace std;

int main() {

    const char* _user = "FOO";
    const char* _password = "BAR";
    const char* _session = "ABCDEFGHIJ";
    int _expectedSeq = 123;

    char login[38];
    sprintf(login,
            "L%-6s%-10s%10s%10d\xA",
            _user,
            _password,
            _session,
            _expectedSeq);

    for (int i = 0; i < 38; i++) {
        printf(" %02x", login[i] & 0xff);
    }

    return 0;
}

Question

Deep in our application code, I came across this:

char login[38];
sprintf(login,
        "L%-6s%-10s%10s%10d\xA",
        _user,
        _password,
        _session,
        _expectedSeq);

Now, I need to write a (simpler) variant of this code:

char login[21];
sprintf(login,
       "\x15\x00\x01%-8s%-10s",
       _user,
       _password);

Somehow, this doesn't work! What's weird is that the latter produces different results every time.


Thoughts

  1. The former example only has a hex literal at the end. Is this masking the issue in the former's case?

  2. Or, am I actually messing up my debug output, printf? (By the way, I got the & 0xff thing from Printing hexadecimal characters in C.)

  3. Could it have something to do with using char instead of unsigned char? But then, why does the former case work?

Community
  • 1
  • 1
Andrew Cheong
  • 29,362
  • 15
  • 90
  • 145
  • 2
    Wouldn't a null character `"\x00"` mean that your format string is terminated prematurely? (Considering `sprintf` only understands null-terminated strings…) – Rufflewind Dec 16 '14 at 23:52
  • Addition to the answer below: Without termination, your `login` would be too short to hold everything -> undefined behaviour ("if" this was the case). (Well, now it´s in the answer too) – deviantfan Dec 16 '14 at 23:54
  • 1
    btw, you're overrunning your array bounds in the first one. The line array is 38 chars long, meaning 37+1 (the 1 being the terminator, the 37 being the allowed maximum length). The sample output you linked is 38 printed bytes, meaning you're sprintf is writing **39** including the terminator, thereby invoking UB. – WhozCraig Dec 16 '14 at 23:56
  • Unless I'm mistaken, `sprintf` doesn't null terminate automatically, so in our case we're okay. That buffer is fed directly to a network handle to which we also pass the number of bytes to send, _i.e._ 21 or 38. (Not saying I think this is how things should be done—just how things are. We don't even use `bool` because it didn't exist when my company started.) – Andrew Cheong Dec 17 '14 at 00:03
  • @AndrewCheong Wrong, the termination is required. Look at eg. N1570, 7.21.6.6.2 – deviantfan Dec 17 '14 at 02:36
  • 1
    'sprintf' definitely will terminate. 'snprintf' won't null terminate if the max length is reached and that might be what is confusing? – GarMan Dec 17 '14 at 03:38
  • @deviantfan - Ah, you're right. Looks like we're writing past the memory space then. Oops. – Andrew Cheong Dec 17 '14 at 13:19

2 Answers2

6

The problem is that your string literal has an embedded NUL byte, and that marks the end of the string as far as sprintf is concerned. So your call is identical to:

sprintf(login,
       "\x15",
       _user,
       _password);

And that writes into the login array only two bytes: 0x15 0x00.

There are several approaches to solve this mixing of bytes and characters. My choice would be something along the lines of:

memcpy(login, "\x15\x00\x01", 3);
sprintf(login + 3,
   "%-8s%-10s",
   _user,
   _password);    

The call to memcpy takes as parameter the number of bytes, so it is immune to the embedded NUL problem.

But note that sprintf automaticall adds a NUL byte at the end of the output string, so you actually need 22 bytes: 3 + 8 + 10 + 1 = 22:

char login[22];
rodrigo
  • 94,151
  • 12
  • 143
  • 190
2

Your issue is that second format string contains a null character (\x00) which terminates it prematurely. Change the string to use %c instead and have a null character printed there.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380