Hacker News new | ask | show | jobs
by shakna 2337 days ago
displayln is a _Generic.

All of these are valid:

    displayln("Hello, World!");
    displayln(100);
    displayln(1.8);
The point is, for simple things, to not have to specify how they appear.

> Well... Because it should have been

> printf("Hello, World!\n");

No. You don't really want to do that. If you're doing that, use puts [0] . All this requires is a modification to one string in memory and you have an injection vulnerability.

[0] http://www.cplusplus.com/reference/cstdio/puts/

5 comments

GCC automatically replaces printf("foo\n") with puts("foo") even with -O0. Clang does it too, albeit I have to enable optimizations: https://godbolt.org/z/drw4xP . As a result I never use puts for literal strings, this way if I want to add dynamic parameters later I don't have to change the function call.

I'm pathologically lazy.

Also as others point out typically the literal string will be in a ro segment so tampering with it won't be easy unless the code runs in a rather exotic environment.

The good practice that you seem to have read about and badly misunderstood is that the format string argument to a printf family function should always be a string literal right there in the code calling it. The concept of changing printf("foo") to printf("%s", "foo") for 'security' against your own hardcoded string literal is more bizarre than any of the intentionally bizarre convolutions in your posted project.
> No. You don't really want to do that. If you're doing that, use puts

This is not the case for RO strings, is it?

You're not guaranteed to have a RO string by the standard are you?
Good point. I suppose that you are talking about overwriting terminating NUL, do I get it right? puts() is vulnurable in exactly same way.
No, a format string attack. If you replace the start of the string with various specifiers, you can lift out pointer addresses and write to them. You can't do that with puts. Worst you can do with puts is read.
OK, understood. So what you meant was more like

   puts()'s attack surface is smaller than printf()'s.
This is not how your message appeared to me - "printf() is vulnerable to injections, use puts() instead". Both are vulnerable to "unintended read()-s".
Not really.

printf is vulnerable to both read and _write_ attacks when you misuse it by only supplying the single argument. It's vulnerable to injections that can lead to remote execution and all sorts of CVEs.

puts is sometimes vulnerable to read attacks, but not often.

If the string was in a variable, yes, but in this case most implementations will put it in a RO section.
That'll be depending on undefined behaviour, though, correct?
That's not UB, that's implementation dependent, AFAIK the C standard says nothing about read-only memory. Attempting to modify a string literal is indeed UB but that would only happen if an attacker managed to attempt to modify the string, not when the program is used normally.
> That's not UB, that's implementation dependent, AFAIK the C standard says nothing about read-only memory.

If we're being pedantic, it's _unspecified behaviour_. The implementation isn't required to document how it would behave.

Other important security features like N^X are also not specified in the C standard either, so what's the point of worrying about just printf format strings?
TBH it’s not pedantic to understand the difference between undefined, implementation-defined, and unspecified behavior. It’s part of knowing the language. One of my standard interview questions for C candidates is to ask them to describe and provide an example of each.
Is there really a meaningful difference between implementation-defined and unspecified? I'd say they're shades of gray based on how pedantic the compiler documentation is.
Is it really not UB to attempt to modify RO things? Typically (on major implementations, e.g. GNU/Linux or MSVC/Windows) it will crash, and I don't think it is allowed to crash on non-UB things?
Can you please exemplify how you exploit a printf("Hello, World!\n") ?
That's a format string attack [0].

By modifying the start of that string, you can begin reading and writing to various parts of the stack.

Whilst implementations may inline that string into a RO memory region - that's not defined behaviour, so you shouldn't depend on it.

[0] https://owasp.org/www-community/attacks/Format_string_attack

> By modifying the start of that string

In order to modify that string, even in RW pages, the attacker already has to have access, at which point the point is moot. It's like saying "if you can change memory, then you can change memory"....

Agreed, if the attacker can modify string constants is,

    printf("Hello, World!\n");
really any safer than this?

    printf("%s\n", "Hello, World!");
No, they’re equivalent in terms of security.

The article’s author (posting here on HN) is grossly mistaken.

They are nearly equivalent in terms of functional security.

Function isn't everything though. One example shows an awareness of the security issue and good habit being used despite the low impact. I'd argue that there is a security benefit to using one over the other.

Additionally, it's not as simple as saying "if you can change memory, then you can change memory". Memory exploits are quite often chains of small issues these days and not the simple buffer overflow of old.

For example, being able to overwrite one byte somewhere could lead to the ability to change only part of a variable address. That could be used to redirect a write to the constant string in memory.

Sure it's contrived, but scenarios like this do happen.

By that same logic, puts("Hello, world!"); is also vulnerable to DoS attack and information leak since someone could have removed the NUL terminator at the end of the string and have puts() read uninitialized/unmapped memory. Which is absurd logic.
Format string attacks have occurred in the wild. [0]

> Originally thought harmless, format string exploits can be used to crash a program or to execute harmful code.

They are not the same as puts. Puts can allow you to potentially read memory.

A format string attack can allow you to write to memory.

[0] https://en.wikipedia.org/wiki/Uncontrolled_format_string

Yes if the attacker has control of the string (like if you do printf(getenv("FOO")) or something equally stupid).
So an attacker able to write to memory would be able to elevate into the ability to... write to memory. That doesn't sound particularly worrisome.
In a lot of cases the attacker can only write to a limited range of memory addresses. If that string happens to fall in that range, they can use it to write to other addresses and/or find out where in memory certain things are stored.

So their ability to write to a limited range of addresses can be extended to a larger range.

And what, pray tell, stops me from modifying the memory at your “%s” string’s location in memory?

Nothing.

Neither is more secure, all modern compilers put both “%s” and “Hello, world!” in rodata sections.

Your understanding of practical format string attacks is misguided.

It's also not defined whether the executable code is in RO memory or RW memory. By your argument, we should also be concerned that the attacker could modify the code directly.