"Save 38% now" C 字符串中止程序
"Save 38% now" C string aborts the program
TL;DR
C 字符串 "% n"
、在 '%'
和 'n'
字符之间带有 space(如“Save 38 % now" string) 被视为 "%n"
,这被认为是自最新 OS 版本以来的一个漏洞,它会导致程序中止。
(请参阅 UPD 1 和 UPD 2)
背景
我正在调查我的应用程序中的日志记录问题。该应用程序从服务器接收一些字符串,我们使用类似于 NSLog
的函数记录这些字符串。我们调用该函数的格式是:
MyLog(@"%@", message);
在一种情况下,消息包含文本“立即节省 38%”。此时应用程序崩溃。
问题
在调查过程中,我能够查明问题所在。完整代码如下。
#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
在任何地方实例化class,
MyClass *my = [[MyClass alloc] init];
,应用程序将在 printf(foo);
时崩溃并显示消息:
%n used in a non-immutable format string
基本上,在“%”和“n”之间带有 space 的字符串“% n”被威胁为“%n”,这被认为是自最新 OS 版本以来的漏洞.
讨论
我仍在努力证明我的想法并找到解决问题的方法...
我相信,导致崩溃的代码是这样的:https://opensource.apple.com/source/Libc/Libc-1244.30.3/stdio/FreeBSD/vfprintf.c我还好吗?
为什么两个字符之间的space对代码没有任何影响?
我怎么可能使用相同的方法解决它?
...到目前为止,它看起来像一个错误。
解决方案
根据社区给出的答案,现在我的解决方法是这个问题(另请参阅“UPD”部分,我在该部分添加了问题最初发生的实际代码):
message = [plainText stringByReplacingOccurrencesOfString:@"%"
withString:@"%%"
options:NSRegularExpressionSearch
range:NSMakeRange(0, message.length)];
DebugOnlyLog(@"%@", message);
更新 1
这是我用来记录一些的代码
#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]);\
}
更新 2
经过社区的回应,我们发现这里有两个问题:
- 将格式字符串传递到
print()
函数的方法存在问题。
- 以类似的方式使用私有 API (
_os_log_internal(...)
) 而不是 os_log
时出现问题(请参阅“UPD 1”部分,我在其中提供了正在使用的代码).
这完全是错误的,系统会告诉你这样的情况,以及具体原因:
printf(foo);
这是一个典型的安全问题。正确的代码是:
printf("%s", foo);
foo
不是静态字符串,将 foo
传递给执行 %-替换的函数是一个安全错误。
space 无关紧要的原因是 space 是格式字符串的一部分。 % n
是一个包含 space 填充的格式说明符(尽管这对 n
类型没有意义,但它仍然是合法的)。有关 % 说明符的完整说明,请参阅 printf man page。它们非常复杂和强大,这正是它们如此危险的原因,你不能将不受控制的字符串交给它们。
在维基百科的 Uncontrolled format string.
中有关于安全问题的快速描述
在您的 os_log 包装器中,您似乎试图绕过现有的 os_log 定义。首先,您应该使用它而不是深入研究未记录的内部结构。问题(os_log 可能会在编译时更清楚)是您不能在此字段中将 non-static 字符串传递给 os_log。 os_log 非常棘手,并且 non-obvious 对它传递的字符串进行插值。这样做是出于性能和安全原因。直接围绕 os_log 重写,并查看 WWDC video 解释 os_log。
您需要使用 puts
而不是 printf
,因为 printf
期望第一个参数是字符串格式。
puts
只是将纯字符串放入输出。
TL;DR
C 字符串 "% n"
、在 '%'
和 'n'
字符之间带有 space(如“Save 38 % now" string) 被视为 "%n"
,这被认为是自最新 OS 版本以来的一个漏洞,它会导致程序中止。
(请参阅 UPD 1 和 UPD 2)
背景
我正在调查我的应用程序中的日志记录问题。该应用程序从服务器接收一些字符串,我们使用类似于 NSLog
的函数记录这些字符串。我们调用该函数的格式是:
MyLog(@"%@", message);
在一种情况下,消息包含文本“立即节省 38%”。此时应用程序崩溃。
问题
在调查过程中,我能够查明问题所在。完整代码如下。
#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
在任何地方实例化class,
MyClass *my = [[MyClass alloc] init];
,应用程序将在 printf(foo);
时崩溃并显示消息:
%n used in a non-immutable format string
基本上,在“%”和“n”之间带有 space 的字符串“% n”被威胁为“%n”,这被认为是自最新 OS 版本以来的漏洞.
讨论
我仍在努力证明我的想法并找到解决问题的方法...
我相信,导致崩溃的代码是这样的:https://opensource.apple.com/source/Libc/Libc-1244.30.3/stdio/FreeBSD/vfprintf.c我还好吗?
为什么两个字符之间的space对代码没有任何影响?
我怎么可能使用相同的方法解决它?
...到目前为止,它看起来像一个错误。
解决方案
根据社区给出的答案,现在我的解决方法是这个问题(另请参阅“UPD”部分,我在该部分添加了问题最初发生的实际代码):
message = [plainText stringByReplacingOccurrencesOfString:@"%"
withString:@"%%"
options:NSRegularExpressionSearch
range:NSMakeRange(0, message.length)];
DebugOnlyLog(@"%@", message);
更新 1
这是我用来记录一些的代码
#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]);\
}
更新 2
经过社区的回应,我们发现这里有两个问题:
- 将格式字符串传递到
print()
函数的方法存在问题。 - 以类似的方式使用私有 API (
_os_log_internal(...)
) 而不是os_log
时出现问题(请参阅“UPD 1”部分,我在其中提供了正在使用的代码).
这完全是错误的,系统会告诉你这样的情况,以及具体原因:
printf(foo);
这是一个典型的安全问题。正确的代码是:
printf("%s", foo);
foo
不是静态字符串,将 foo
传递给执行 %-替换的函数是一个安全错误。
space 无关紧要的原因是 space 是格式字符串的一部分。 % n
是一个包含 space 填充的格式说明符(尽管这对 n
类型没有意义,但它仍然是合法的)。有关 % 说明符的完整说明,请参阅 printf man page。它们非常复杂和强大,这正是它们如此危险的原因,你不能将不受控制的字符串交给它们。
在维基百科的 Uncontrolled format string.
中有关于安全问题的快速描述在您的 os_log 包装器中,您似乎试图绕过现有的 os_log 定义。首先,您应该使用它而不是深入研究未记录的内部结构。问题(os_log 可能会在编译时更清楚)是您不能在此字段中将 non-static 字符串传递给 os_log。 os_log 非常棘手,并且 non-obvious 对它传递的字符串进行插值。这样做是出于性能和安全原因。直接围绕 os_log 重写,并查看 WWDC video 解释 os_log。
您需要使用 puts
而不是 printf
,因为 printf
期望第一个参数是字符串格式。
puts
只是将纯字符串放入输出。