“现在节省 38%”C 字符串中止程序

Yev*_*nin 2 c freebsd objective-c stdio char

TL; 博士

在C字符串"% n"用空格之间'%''n'字符(如在“保存现在38%”的字符串)被视为"%n",这被认为是因为最新的OS版本中的漏洞,并导致中止程序。

(请参阅 UPD 1 和 UPD 2)

背景

我正在调查我的应用程序中的登录问题。该应用程序从服务器接收一些字符串,我们使用类似于NSLog. 我们称该函数的格式为:

MyLog(@"%@", message);
Run Code Online (Sandbox Code Playgroud)

在一种情况下,消息包含文本“现在节省 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
Run Code Online (Sandbox Code Playgroud)

在任何地方实例化类,

MyClass *my = [[MyClass alloc] init];
Run Code Online (Sandbox Code Playgroud)

,应用程序将崩溃并printf(foo);显示以下消息:

%n used in a non-immutable format string
Run Code Online (Sandbox Code Playgroud)

基本上,字符串“% n”,在“%”和“n”之间有一个空格被威胁为“%n”,自最新的操作系统版本以来,这被认为是一个漏洞。

讨论

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

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

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

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

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

解决方案

根据社区给出的答案,现在我对这个问题的解决方法是这样的(另请参阅“UPD”部分,我在最初出现问题的地方添加了实际代码):

message = [plainText stringByReplacingOccurrencesOfString:@"%"
withString:@"%%" 
options:NSRegularExpressionSearch 
range:NSMakeRange(0, message.length)];

DebugOnlyLog(@"%@", message);

Run Code Online (Sandbox Code Playgroud)

更新 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]);\
}
Run Code Online (Sandbox Code Playgroud)

更新版本 2

在社区响应之后,很明显这里有两个问题:

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

注意:我也在Apple Developer forum发布了同样的问题

Rob*_*ier 9

这完全是错误的,系统会这样告诉您,以及确切原因:

printf(foo);
Run Code Online (Sandbox Code Playgroud)

这是一个经典的安全问题。正确的代码是:

printf("%s", foo);
Run Code Online (Sandbox Code Playgroud)

foo不是静态字符串,传递foo给执行 %-substitutions 的函数是一个安全错误。

空格无关紧要的原因是因为空格是格式字符串的一部分。% n是一个包含空格填充的格式说明符(尽管这对n类型没有意义,但它仍然是合法的)。有关% 说明符的完整说明,请参见printf 手册页。它们非常复杂和强大,这正是它们如此危险的原因,您不能将不受控制的弦交给它们。

维基百科的不受控制的格式字符串中有对安全问题的快速描述。


在您的 os_log 包装器中,您似乎试图绕过现有的 os_log 定义。首先,您应该使用它而不是触及未记录的内部结构。问题(os_log 在编译时可能会更清楚)是您不能在此字段中将非静态字符串传递给 os_log。os_log 对其传递的字符串执行非常棘手且不明显的插值。它这样做是出于性能和安全原因。直接围绕os_log重写这个,看WWDC视频解释os_log。