@Lenciel

马尔克斯的孤独与爱

Don't touch me

度过了忙乱的一周,又回到了广汉。吃完饭洗个澡,继续在书房干活,外面的天不知不觉就黯淡下来。偶像派走过来说,《舌尖上的中国》第二季开播了,快来看吧。

但我总觉得有什么没有做,合上电脑之前,突然想起来,对,是马尔克斯没了。早上听到这个消息的时候,我好像想着要说点儿什么来着的。

我最喜欢的作家是马尔克斯,但我一般不会推荐别人去读他的小说。有人问我谁的小说比较好看时,我一般都推荐村上春树。

在我看来,什么是好的小说呢?像《白鹿原》、《平凡的世界》那些当然是非常好的小说,但这些小说你读起来更多是在领略,而不是在参与。换句话说,那些是适合十几岁的时候看的东西。

而活到一定年纪,你就会发现,人生就像在爬山,因为路太窄,加上不是在上坡,就是在下坡,很难在你想策马奔腾的时候就能找到一块平坦的地方,让你潇洒一番:大多数时候你不过是孤孤单单地走在爬山的路上而已。

问我的人大都和我差不多年纪,在城市里面生活,我想他们的体会应该和我类似。而在我眼中,村上春树把城市生活里的孤独感捕捉并表达到了无论是什么背景的读者都会有所共鸣的地步,所以我推荐他的小说。

但村上的小说,虽然我非常喜欢,为什么只能排在马尔克斯后面呢?大概和他有些刻板的写作生活有关,村上的作品会让你感觉到从孤独到弃绝的意味。换句话说,你经常能被他营造的孤独感吞没,他却没有告诉你应该怎么办:他自己呢,倒是知道避开作家圈子,知道去听歌跑步健身,知道在鸡蛋和高墙之间如何选择。

而马尔克斯则大不相同。在很多时候,他被书商打上了孤独者的标签贩卖(在我们这个年代,孤独者的孤独也成了时髦,真是让孤独者无处可逃啊)。他下面这段话,也随着《百年孤独》在国内广为传颂:

过去都是假的,回忆是一条没有归途的路,以往的一切春天都无法复原,即使最狂乱且坚韧的爱情,归根结底也不过是一种瞬息即逝的现实,唯有孤独永恒。

但看过《百年孤独》的人都知道,那其实是个非常热闹的故事。换句话说,虽然是写出了前面这段带着些孤独宿命论的话,马尔克斯并不是仅仅靠营造孤独感来引起你共鸣:他在劝你好好去爱。

孤独的确是人的宿命:我们每个人都是这世界上一个偶然的存在,生命如此漫长又短暂,我们一直在得到和失去,汲取和忘却,不要说别人,我们自己也不一定来得及了解自己。

很多人以为对抗孤独,需要绝对的爱,比如男女之间那种想要燃尽各自的肉体和精神的完全排他性的爱,或者父母孩子之间那种完全忘我不计付出的爱。

但无论村上还是马尔克斯讲诉的故事,都告诉你即便是这些绝对的爱,也难以与岁月相抗衡。人们对此心知肚明,可是往往还是憧憬着绝对的爱,对自己所拥有的「有瑕疵的爱」感到不满足。

于是马尔克斯更进一步,他告诉你那份孤独恰恰是爱的最意味深长的赠品。你接受了这份赠品,就能学会理解别的孤独的灵魂和深藏于它们之中的深邃的爱。读懂他你会明白,是否一起生活一起死去并不重要,能够在他/她到来的时候好好去爱,其充实感是任何体验都无法比拟的。从某种意义上说,那么强烈的感情正是人的一生中最美好的东西。

所以,并没有像电池一样的东西在持续供给着让你对抗孤独。那些某个瞬间点燃的火花,就足够照亮我们漫长的日常生活。

Openssl Heartbleed Bug

连某宝都中招的Heartbleed bug究竟是个什么东西?简单地说就是攻击者可以读最多 64KB 内存的内容。

读了这 64KB 能干嘛?用报这个 bug 的人的话来说:

Without using any privileged information or credentials we were able steal from ourselves the secret keys used for our X.509 certificates, user names and passwords, instant messages, emails and business critical documents and communication.

那么读取 64KB 内存和获取这么多关键信息究竟有什么关系呢?

The bug

先来看看patch里面的ssl/d1_both.c:

int
dtls1_process_heartbeat(SSL *s)
    {
    unsigned char *p = &s->s3->rrec.data[0], *pl;
    unsigned short hbtype;
    unsigned int payload;
    unsigned int padding = 16; /* Use minimum padding */

可以看到,heartbeat 里有一个 SSLv3 record 的指针,这个record的代码如下:

typedef struct ssl3_record_st
    {
        int type;               /* type of record */
        unsigned int length;    /* How many bytes available */
        unsigned int off;       /* read/write offset into 'buf' */
        unsigned char *data;    /* pointer to the record data */
        unsigned char *input;   /* where the decode bytes are */
        unsigned char *comp;    /* only used with decompression - malloc()ed */
        unsigned long epoch;    /* epoch number, needed by DTLS1 */
        unsigned char seq_num[8]; /* sequence number, needed by DTLS1 */
    } SSL3_RECORD;

可以看到,每个record有它的typelengthdata,规规矩矩。

回到dtls1_process_heartbeat

/* Read type and payload length first */
hbtype = *p++;
n2s(p, payload);
pl = p;

可以看到SSLv3 record的第一个 byte 就是放这个heartbeattype。 宏n2s 则是从p里面取两个 byte 放到 payload 里面,被用来作为 payload 的长度。 注意这里并没有检查SSLv3 record 实际的长度。

接下来在这个函数里面干了下面这些事情:

unsigned char *buffer, *bp;
int r;

/* Allocate memory for the response, size is 1 byte
 * message type, plus 2 bytes payload length, plus
 * payload, plus padding
 */
buffer = OPENSSL_malloc(1 + 2 + payload + padding);
bp = buffer;

可以看到,用户要多少程序就分配多少,最多可以分配到65535+1+2+16,指针 bp 被用来操作这块内存。然后:

/* Enter response type, length and copy payload */
*bp++ = TLS1_HB_RESPONSE;
s2n(payload, bp);
memcpy(bp, pl, payload);

s2nn2s做的操作恢复出来:先拿 16 个 bit 的值放到 2 个 byte 里面,也就是原来请求的 payload 的长度。然后把pl里面放的 payload(请求者提交的 data)拷贝到新分配的bp里面。

看起来是很平常的操作,只不过没有认真的检查用户输入而已,但问题也就在这里了。

Where is the bug

如果用户并没有正在提交声称的那么多个 bytes 的 payload,那么 memcpy 就会读到同一个 process 里面 SSLv3 record 附近的内存内容。

这附近有哪些内容呢?

首先要明白在 linux 上,内存的动态分配主要是通过sbrk 或者是 mmap。如果内存是通过 sbrk 分配的,它会使用heap-grows-up规则,泄露出来的东西不会那么多(但是如果是同时并发请求还是有东西会漏)。

在这里,pl因为 malloc 里面的 mmap_threshhold 多半是 sbrk 分配的,但是,那些关键的用户数据,则多半是通过 mmap 分配内存。于是这些数据就会被攻击者用pl拿到。如果再考虑并发请求,就…

The fix

所以,整个 patch 里面最主要的 fix 就是:

  • 检查是否有长度为 0 的虚假 heartbeat
  • 检查 record 的真实长度

代码如下:

    /* Read type and payload length first */
    if (1 + 2 + 16 > s->s3->rrec.length)
        return 0; /* silently discard */
    hbtype = *p++;
    n2s(p, payload);
    if (1 + 2 + payload + 16 > s->s3->rrec.length)
        return 0; /* silently discard per RFC 6520 sec. 4 */
    pl = p;

So?

这个 bug 大概算是影响这么剧烈的 bug 里面最好明白的一个,所以居然我也看明白了。感受:

  • 为了可扩展性引入了复杂度,经常都会带来恶梦
  • 用户的输入,无论如何都不能相信,一定要 check
  • C 语言的确是大牛小牛都会踩到坑啊