TL;DR
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)
Background
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.
Issue
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.
Discussion
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.
Solution
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);
UPD 1
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]);\
}
UPD 2
After the community response, it has become clear that there are two issues here:
- There is a problem with the approach of passing format string into
print()function. - There is a problem in using private API (
_os_log_internal(...)) instead ofos_login 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.