The C string "% n"
, with a space between '%'
and 'n'
characters (like in "Save 38% now" string) is treated as "%n"
, which is considered a vulnerability since latest OS releases, and it leads to abort the program.
(please see UPD 1 and UPD 2)
I am investigating an issue with the logging in my app. The app receives some string from the server, which we log with function similar to NSLog
. The format we call that function is:
MyLog(@"%@", message);
In one case, message contains text "Save 38% now". At this point the app crashes.
While looking into it I was able to pinpoint the issue. The full code is below.
#define FOO(FORMAT, ...) (\
{\
char *str;\
str = [[NSString stringWithFormat:FORMAT, ##VA_ARGS] UTF8String];\
str;\
}\
)\
#import "MyClass.h"
@implementation MyClass
- (instancetype)init
{
self = [super init];
if (self) {
char *foo = FOO(@"%@", @"Save 38% now");
printf(foo);
}
return self;
}
@end
Instantiate class anywhere,
MyClass *my = [[MyClass alloc] init];
, and the app will crash upon printf(foo);
with the message:
%n used in a non-immutable format string
Basically, the string "% n", with a space between "%" and "n" is threated as "%n", which is considered a vulnerability since latest OS releases.
I am still trying to proof my thoughts and find a way to solve the issue...
I believe, the code responsible for the crash is this: https://opensource.apple.com/source/Libc/Libc-1244.30.3/stdio/FreeBSD/vfprintf.c Am I risgt?
Why the space between two characters does not make any difference for the code?
How could I possibly work around it leaving the same approach?
...so far, it looks like a bug.
Based on answers given by the community, right now my workaround to the issue is this (see also "UPD" part where I added the actual code where the issue originally occurred):
message = [plainText stringByReplacingOccurrencesOfString:@"%"
withString:@"%%"
options:NSRegularExpressionSearch
range:NSMakeRange(0, message.length)];
DebugOnlyLog(@"%@", message);
This is the code I use to log some
#import <os/log.h>
extern struct os_log_s _os_log_default;
extern __attribute__((weak)) void _os_log_internal(void *dso, os_log_t log, os_log_type_t type, const char *message, ...);
#define DebugOnlyLog(FORMAT, ...) \
void(*ptr_os_log_internal)(void *, __strong os_log_t, os_log_type_t type, const char *, ...) = _os_log_internal;\
if (ptr_os_log_internal != NULL) {\
_os_log_internal(&__dso_handle, OS_OBJECT_GLOBAL_OBJECT(os_log_t, _os_log_default), 0x00, [[NSString stringWithFormat:FORMAT, ##__VA_ARGS__] UTF8String]);\
}
After the community response, it has become clear that there are two issues here:
print()
function._os_log_internal(...)
) instead of os_log
in similar fashion (see the "UPD 1" section, where I provided the code being used).Note: I also posted the same question to Apple Developer forum.
This is simply wrong, and the system is telling you so, and exactly why:
printf(foo);
This is a classic security problem. The correct code is:
printf("%s", foo);
foo
is not a static string, and it is a security mistake to pass foo
to a function that performs %-substitutions.
The reason a space doesn't matter is because the space is part of the format string. % n
is a format specifier that includes space padding (even though that doesn't make sense for the n
type, it's still legal). See the printf man page for a full description of the % specifiers. They're quite complicated and powerful, which is exactly why they're so dangerous, and you can't hand uncontrolled strings to them.
There's a quick description of the security problem at Wikipedia's Uncontrolled format string.
In your os_log wrapper, you seem to be trying to bypass the existing os_log define. First, you should use that rather than reaching into undocumented internals. The problem (which os_log likely would have made clearer at compile time) is that you cannot pass a non-static string to os_log in this field. os_log does very tricky and non-obvious interpolation on the string it's passed. It does this for both performance and security reasons. Rewrite this around os_log directly, and see the WWDC video explaining os_log.