iOS微信特殊字符保护方案

前言

相信大家都遇到过一段特殊文本可以让iOS设备所有app闪退的经历。前段时间大年初一,又出现某个印度语字符引起iOS11系统奔溃。所幸微信客户端做了保护并没有引起太大问题。一般来说,特殊字符闪退是系统漏洞引起,只要更新系统就行。但大部分用户不愿意更新系统,而苹果也不一定第一时间解决问题。另外后台可以拦截恶意文本传递,但对于本地已下发的消息,后台没有办法让它删除。所以客户端还是要做些保护预防特殊字符闪退。

方案

由于无法事先知道字符串里包含特殊字符,所以只能先让它排版/绘制,看看是否出现问题。做法是,在排版/绘制字符串前,先设置标记位,排版/绘制结束后,移除标记位;一旦发现标记位存在,就意味着这字符串可能有问题,下次就不显示这个字符串:

这里有几个问题:

  1. 有可能在排版/绘制过程中,其它线程crash,导致标记位不能正常移除。所以crash时要判断crash线程是否为排版/绘制线程。
  2. 究竟crash多少次才能判断这字符串是有问题的。最早做法是crash一次就直接屏蔽,但很多用户反馈,说某些好友昵称无法显示。其实iOS绘制字符串时也会极少概率出现闪退,从而误判。但crash两次才屏蔽的话,如果用户连续收到N条恶意消息,那么至少crash 2N次才彻底把所有有问题消息屏蔽。因此,第一次字符串crash先不屏蔽,后续连续字符串crash的话,直接屏蔽。这样crash N+1次就能处理完了。

整个逻辑代码大致如下:

// MessageItemView.mm, CP是CrashProtected的简称

@implementation MessageItemView

- (void)initContentLabel {
    m_label = [[MMCPLabel alloc] init];
    m_label.cpKey = [MMCPUtil generateKeyWithObject:self.messageModel];
    
    if ([MMCPUtil isUnsafeWithKey:m_label.cpKey]) {
        // 检测出messageModel消息内容有问题,屏蔽显示
        m_label.text = @"该内容无法显示";
    } else {
        m_label.text = self.messageModel.content;
    }
}

@end
// MMCPLabel.mm

@implementation MMCPLabel

@synthesize cpKey = m_cpKey;

// 对常用的排版/绘制接口做检查
- (void)layoutSublayersOfLayer:(CALayer *)layer {
    CScopedCrashCounter crashCounter(m_cpKey);
    [super layoutSublayersOfLayer:layer];
}

- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
    CScopedCrashCounter crashCounter(m_cpKey);
    [super drawLayer:layer inContext:ctx];
}

- (CGSize)sizeThatFits:(CGSize)size {
    CScopedCrashCounter crashCounter(m_cpKey);
    return [super sizeThatFits:size];
}

@end
// MMCPUtil.mm

// 利用C++特性,在声明C++类临时变量时,会自动执行构造函数,离开作用域会执行析构函数
// 因此构造函数做crashCount+1,析构函数做crashCount-1
class CScopedCrashCounter {
public:
    CScopedCrashCounter(NSString *cpKey) {
        m_cpKey = cpKey;
        [MMCPUtil increaseCrashCountWithKey:m_cpKey];
    }
    ~CScopedCrashCounter() {
        [MMCPUtil decreaseCrashCountWithKey:m_cpKey];
    }
private:
    NSString *m_cpKey;
};

@implementation MMCPUtil

@synthesize crashKeyMemoryMappedKV = m_crashKeyMemoryMappedKV; // 被判定为恶意信息对应的key
@synthesize crashCountMemoryMappedKV = m_crashCountMemoryMappedKV; // 每个key crash次数

- (BOOL)isUnsafeWithKey:(NSString *)key {
    return [m_crashKeyMemoryMappedKV getBoolForKey:key] == YES;
}

- (void)increaseCrashCountWithKey:(NSString *)key {
    // 这里记录key所在线程
    ...

    int32_t count = [m_crashCountMemoryMappedKV getInt32ForKey:key];
    [m_crashCountMemoryMappedKV setInt32:count + 1 forKey:key]
}

- (void)decreaseCrashCountWithKey:(NSString *)key {
    int32_t count = [m_crashCountMemoryMappedKV getInt32ForKey:key];
    [m_crashCountMemoryMappedKV setInt32:MAX(0, count - 1) forKey:key];
}

// crash回调函数
- (void)onSignalCrash:(siginfo_t *)info {
    // 先找到跟crash线程相同的key
    NSString *key = [self lastCPKey:info->si_pid];
    if (key == nil) return;
    
    if (m_isLastTimeCrashedBySpecialCharacter == NO) {
        // 设置当前是特殊字符引起的闪退,如果crash次数大于1,则屏蔽这字符串显示
        [self setLastTimeCrashedBySpecialCharacter:YES];
        if ([m_crashCountMemoryMappedKV getInt32ForKey:key] > 1) {
            [m_crashKeyMemoryMappedKV setBool:YES forKey:key];
        }
    } else {
        // 连续特殊字符闪退,直接屏蔽
        [m_crashKeyMemoryMappedKV setBool:YES forKey:key];
    }
}

@end

即使有了上面的N+1优化,当N很大时,客户端还是要crash很多次才能正常使用。之前有用户乱扫二维码被拉进炸群,如果不发红包,群主不停炸群;用户频繁crash,也无法退群。不少用户会选择卸载重装客户端。因此客户端要加上安全模式的机制。当客户端检测出连续三次crash,下次启动会出现安全模式的界面,提示用户如何处理:

对于频繁闪退的群聊,主界面提供快捷入口方便用户退群。另外对于可能误判的字符串,界面也提供入口方便用户恢复字符串显示:

为了让后台第一时间发现新的特殊字符变种,客户端检测出特殊字符crash后,会把相关信息上报到后台。通过客户端上报、后台拦截的闭环,能大大降低特殊字符传播范围。这方案不仅用于特殊字符,还能用于其他恶意信息,如炸群消息、GIF、小视频、链接等。

MemoryMappedKV

由于需要埋点的地方太多了,昵称、消息内容、头像等等,为了不影响滑动性能,guoling同学开发了一套基于mmap的高性能通用key-value存储组件,敬请留意WeMobileDev后续文章。

原文发布于微信公众号 - WeMobileDev(WeMobileDev)

原文发表时间:2018-03-12

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏一个番茄说

Swift中防止ptrace依附

在移动开发中,安全是一个很重要的话题,当然安全是没有绝对的,只能说尽可能的提高安全性。在iOS的开发中,为了防止别人窥视我们的App,我们得采用一些手段来进行防...

1223
来自专栏张善友的专栏

CLR 4.0 安全模型

在公共语言运行时(CLR)过往的版本中,安全模型一直是最为复杂的模块之一,由于涉及Evidence,CAS策略等机制,难以被用户使用。在Silverlight中...

1938
来自专栏武军超python专栏

2018年8月18日初识tkinter

把C盘里面的东西移动到其他盘对文件有影响吗?普通文件如音频视频没有影响,但是如果是软件的话 下载的时候会在注册表中记录打开文件的路径,如果移动到其他盘的话注册...

1102
来自专栏前端杂货铺

Blob初探

简介   Blob在js中意味着二进制大数据。实现该接口的对象有3个属性,分别是type(MIME),size(byte)和 一个切割方法:slice(在大文件...

3503
来自专栏有趣的Python和你

Python数据分析之贴吧的问与答读取数据库获取question列分词词云

1253
来自专栏微信公众号:Java团长

即将发布的 JDK 10 有 109 项新特性,你喜欢哪些?

按计划,JDK 10 将于 3 月 20 日正式发布。据前 Oracle 员工 Simon Ritter 的统计,JDK 10 总共包含 109 项新特性。当然...

652
来自专栏林冠宏的技术文章

关于Android中为什么主线程不会因为Looper.loop()里的死循环卡死?引发的思考,事实可能不是一个 epoll 那么 简单。

( 转载请务必标明出处:https://cloud.tencent.com/developer/user/1148436/activities) 前序 本文将...

3285
来自专栏IMWeb前端团队

React函数式进阶

React让很多人让追捧的一个特性是它的所有的组件都是完全由JavaScript组成的。组件的定义是JavaScript,组件的模板也可以是JavaScript...

2356
来自专栏GopherCoder

『No19: Gorm 上手指南』

如果你是做后端开发的,日常工作中,除了熟悉编程语言之外,数据库怕是最常用的技术了吧。

6681
来自专栏菩提树下的杨过

更好用的excel国际化多语言导出

不知道大家在开发中有没有遇到过『excel导出』的需求,反正我最近写了不少这种功能,刚开始利用poi,一行行的手动塞数据,生成excel,而且还有国际化需求,比...

1382

扫码关注云+社区

领取腾讯云代金券