Jump to content
  • 0

Wrapper for printf to filter string



Hi all,

I'm trying to implement a wrapper for printf, which filters the string to prevent special non-printable characters. The background behind this is a console output for a secure software where the secure output should be preceded by "SECURE:" and the non-secure output should be preceded by "NONSECURE". I want to prevent someone to insert some control chars to overwrite the prefix (like including an '\r' at the beginning.)

So far, I found a good example for a wrapper:

void nonsec_printf(char *string, ...) {
	va_list argp;
	fprintf(stdout, "NONSEC: ");
	va_start(argp, string);
	vfprintf(stdout, string, argp);

How could I check the parameters for any special characters? Or should I rather start one level deeper at putc...

Link to comment
Share on other sites

10 answers to this question

Recommended Posts

Oh dear, printf() seems to be a big security hole. Reading into the matter by googling around, this is known since 1989. I'd like to share my findings:

printf() is a variadic function, that means it accepts a variable amount of arguments. Definition of such a function is

void function (int foo, ...);

It has to have a least one named parameter and the ellipsis parameter ("...") has to be at the end. To access the parameters, macros defined in <stdarg.h> can be used va_start, va_list, va_arg, va_end.

So, as I already found out, I can pass a list of arguments va_list to vprintf() (which accepts a va_list instead of a string) to build a wrapper for my printf. However, the very sad part is, that va_list is only a pointer to the beginning of the list, so you can't determine the end without a convention with the caller.

This is the weakness many exploits use to read and write arbitrary memory. printf() as a variadic function can't know about the number of arguments passed in a call. So if you type:

printf("This is column %d and row %d", column);

You have a mismatch between the number of placeholders and the number of arguments. printf() fetches the arguments from stack, not knowing how many "legal" arguments have been placed there:

Stack ------ (grows in this direction) ----->

.... | Argument c | Argument b | Argument a | Address of Format String | ...

<--- (moving in this direction) ---  ^
                                     printf()'s internal pointer

So with printf and a tricky format string, we basically can read the stack! More evil, printf accepts format parameters like %s, which treats the argument as an address of a string, which has to read out until the next \0 (NULL) character. Both combined, we are able to place an address on the stack and then read out this address with %s:

printf ("\x10\x01\x48\x08 %x %x %x %x %s");

But printf() isn't limited to read access of arbitrary memory locations, it can even write to memory with %n, this format parameter writes the number of characters written so far. With the same example from above, replacing %s by %n,  the contents of that address will be overwritten.


So, back to my initial idea, replacing the "NONSECURE" string by adding an "\r" and overwrite the preceding string seems to be my least problem if I build a wrapper for printf().

This concept is meant to be used for the brand new Cortex M23 architecture, which has a Trustzone with a secure library and hardware monitoring and a non secure part. The wrapper is a non secure call to be able to have printf available in the non secure part without having access to the library nor the hardware. For sure, only allowing constant strings would be easy.

The good thing is, I have access to the format string in my wrapper, so I should be able to filter out all malicious %s and %n format parameters. Leaving the possibility to read the stack. Will this be secure enough? The wrapper will be called in the secure world, so being able to read out the secure stack might not be the best idea.

I'd be happy if someone could add some thoughts about this, my project is on Github, please have a look: https://github.com/dxstp/saml11_init/blob/master/SecureProject/utils/print.c.



Link to comment
Share on other sites

  • Member

I am not clear on what you are trying to achieve here? The reality is that even if you give a secure wrapper anyone could still call the insecure printf directly. If they wanted to be malicious in this case they could just do a call with an address of your function incremented by a couple of instructions to skip your checks?

I think the way to make it "secure" in a way is for you to simply process the format strings and limit what can be passed into these. If you do a secure function protected by trustzone or something you need to make a function which has the format string either fixed or severely limited.

That said, all of this is not going to help you if they use a side-channel attack to just read all of memory ...


Link to comment
Share on other sites

Well, the initial idea of simply filtering out a carriage return turned way more complicated than I thought. Let me explain the background, but first let's put aside hardware tampering and side-channel attacks, this is about software security along with a Trustzone.

I'm writing this software as an exercise for me to understand the SAML11, which has a Trustzone where I'd like to put all the secure communication functions. One of them has access to a console via UART. So the secure part of the software is able to use printf() and can output a string to the console. For better distinguishability, the printf precedes a "SECURE" string before each output of the secure app.

The non-secure app is running in a different part of the memory, has it's own stack, it's own program counter and a kind of hypervisor (IDAU) is preventing this app from accessing the memory of the secure app in any way. Additionally, it won't have access to any peripherals or registers which aren't declared non-secure before by the secure app. To control access to the outside world, I can implement so called non-secure caller functions (NSC function), which are placed in a special (de-militarized?) zone between the secure and the non-secure memory. This functions can be called by the non-secure app and can branch to a secure function (again supervised by hardware) to do something which requires secure privileges and then return to the non-secure app.

So, my first idea was to simply let them use printf and put that into the NSC zone. Each time the non secure app calls my NSC-printf, I simply precede a "NONSECURE" to mark this line coming from the non secure app. The first post shows my implementation. But then I have to control the format string to prevent the non-secure app from overwriting my prefix, I thought. And THEN I realized how much you can do with %s, %x and %n :classic_rolleyes:

I have different options now. The safest way would be to just print out the format string without bothering with the arguments, like printf("%s", nonsec_string). In this way, I only allow constant strings, so I could just write my own function like printc(const char *) and forget about stdlib, saving space. The second way would be to severely limit the capabilities of printf and filter out all malicious format placeholders and only allow a handful of them. Like if it's not a 'd' or a '%' what follows a '%', replace the '%' with '_'.

And yes, the non-secure app could have an own implementation of printf(), but that would just allow them to explore the non-secure memory, which yields nothing. Even if they could reach memory parts which are placed in the secure zone by this method, that would lead to a hardfault by the hypervisor. However, a secure function is execute inside the secure memory, so an uncontrollable format string would pose a serious threat.

What do you think - does it still make sense to filter the format string in a wrapper or should I just expose a function to output constant strings, length limited, stripped from non-printable chars?

Link to comment
Share on other sites

  • Member

printf will bind to stdio by simply calling putchar repeatedly. If your user will supply the data there is no risk if you run the code in Trustzone as they will not be able to access any data maliciously without causing a hardware fault.

So this means your only remaining problem is ensuring that the user does not print backspace characters or \r\n characters, so you can simply remove/ignore backspace and replace every \r\n with "\r\nSECURE:" and you should be good?

This can all be done quite safely inside of the implementation of putchar, and you can run that inside of Trustzone (which I have not used myself so I do not know the details of the limitations)

Link to comment
Share on other sites

error: 'cmse_nonsecure_entry' attribute not available to functions with variable number of arguments

This is what I saw today on my screen 😂

So I had no other choice than exposing _read and _write as a non-secure entry. The write function filters out all non-printable chars and adds the prefix before the string. Another challenge was to bring printf to not print char by char, I had to turn on line buffering:

setvbuf(stdout, NULL, _IOLBF, 80);

I still have trouble understanding how a function call from non secure to secure and back looks like from the perspective of the stacks (it has two stack pointers). Have a look at my Github if you like, I'm open for critical reviews.

Anyway, filtering printf format strings would work, but gets complicated very quickly with all these possible combinations of parameters, flags, etc. It would quickly end up in building a parser for the format string. I didn't foresee how deep the problem actually is when I started this thread 🙂

Thanks for your input!

Link to comment
Share on other sites

No you haven't, right. This only was my conclusion for my original idea 🙂

The key for filtering the output string was activating line buffering, then the _write function will be called with a larger string which can be filtered well. If buffering is set to off or if filtering is implemented in putchar, you would have to set up a little state machine to keep track of the characters already written.

Link to comment
Share on other sites

  • Member

Well if you just replace the nasty characters you do not need a state machine, but if you want to hold off on sending SECURE: instead of just replacing a \n with that you will need a 2-state machine, hardly worthy of the name, 😉 we should call it a flag then ...

Link to comment
Share on other sites


This topic is now archived and is closed to further replies.


  • Create New...