"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 版本以来的漏洞.

讨论

我仍在努力证明我的想法并找到解决问题的方法...

  1. 我相信,导致崩溃的代码是这样的:https://opensource.apple.com/source/Libc/Libc-1244.30.3/stdio/FreeBSD/vfprintf.c我还好吗?

  2. 为什么两个字符之间的space对代码没有任何影响?

  3. 我怎么可能使用相同的方法解决它?

...到目前为止,它看起来像一个错误。

解决方案

根据社区给出的答案,现在我的解决方法是这个问题(另请参阅“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

经过社区的回应,我们发现这里有两个问题:

  1. 将格式字符串传递到 print() 函数的方法存在问题。
  2. 以类似的方式使用私有 API (_os_log_internal(...)) 而不是 os_log 时出现问题(请参阅“UPD 1”部分,我在其中提供了正在使用的代码).

注意:我也发布了the same question to Apple Developer forum

这完全是错误的,系统会告诉你这样的情况,以及具体原因:

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 只是将纯字符串放入输出。