瓦尼什如何遇上切瑞5/N

涂漆的工作空间

要处理HTTP请求或响应,Varnish必须分配仅在处理期间使用的内存位,并且所有内存都可以同时释放回来。

为了避免来电 malloc(3) 这会带来大量的多线程进程中的锁定开销,但更多的是为了避免必须跟踪所有这些分配以便能够 free(3) 所有这些,Varnish都有“工作空间”:

struct ws {
    […]
    char    *s;     /* (S)tart of buffer */
    char    *f;     /* (F)ree/front pointer */
    char    *r;     /* (R)eserved length */
    char    *e;     /* (E)nd of buffer */
};

这个 s 指针指向由当前线程独占拥有的内存片的起始处,并且 e 指向末尾。

最初 f 与之相同 s ,但当从工作区进行分配时,它会向 e 。这个 r 指针是用来“预订”的,我们暂时忽略它。

工作空间看起来很容易创建:

ws->s = space;
ws->e = ws->s + len;
ws->f = ws->s;
ws->r = NULL;

…只是,考虑到C语言令人目瞪口呆的本质,我们已经系上了很多安全带:

#define WS_ID_SIZE 4

struct ws {
    unsigned        magic;
#define WS_MAGIC    0x35fac554
    char            id[WS_ID_SIZE]; /* identity */
    char            *s;             /* (S)tart of buffer */
    char            *f;             /* (F)ree/front pointer */
    char            *r;             /* (R)eserved length */
    char            *e;             /* (E)nd of buffer */
};

void
WS_Init(struct ws *ws, const char *id, void *space, unsigned len)
{
    unsigned l;

    DSLb(DBG_WORKSPACE,
        "WS_Init(%s, %p, %p, %u)", id, ws, space, len);
    assert(space != NULL);
    assert(PAOK(space));
    INIT_OBJ(ws, WS_MAGIC);
    ws->s = space;
    l = PRNDDN(len - 1);
    ws->e = ws->s + l;
    memset(ws->e, WS_REDZONE_END, len - l);
    ws->f = ws->s;
    assert(id[0] & 0x20);           // cheesy islower()
    bstrcpy(ws->id, id);
    WS_Assert(ws);
}

让我向您介绍一下:

这个 DSLb() 调用可以用来跟踪工作区上的所有操作,因此我们可以看到实际发生了什么。

(提示:您的 malloc(3) 可能有类似的东西,寻找 utrace 在手册页中。)

接下来,我们检查提供的空间指针是否不为空,以及它是否正确对齐,这两者都遵循Varnish样式模式,随意地散布断言,这既是作为代码文档,也是因为它允许编译器更好地优化事情。

这个 INIT_OBJ() and `` Magic``字段是我们在整个Varnish中使用的一种样式模式:每个结构都用唯一的魔术标记,这可以用来确保指针在通过  ``void*

我们设置了 s 指针。

我们计算的长度至少比提供的长度短一个字节,对齐并指向 e 在这方面。

我们填补了过去和过去的额外空间 e ,带有随机检测超限的“金丝雀”。它捕捉到了大部分但不是所有的超限。

我们设置工作区的名称,以确保它尚未标记为已溢出。

最后检查结果工作区是否符合定义的不变量,如 WS_Assert() 功能。

对于Cheri,它看起来是这样的:

void
WS_Init(struct ws *ws, const char *id, void *space, unsigned len)
{
    unsigned l;

    DSLb(DBG_WORKSPACE,
        "WS_Init(%s, %p, %p, %u)", id, ws, space, len);
    assert(space != NULL);
    INIT_OBJ(ws, WS_MAGIC);
    assert(PAOK(space));
    ws->s = cheri_bounds_set(space, len);
    ws->e = ws->s + len
    ws->f = ws->s;
    assert(id[0] & 0x20);           // cheesy islower()
    bstrcpy(ws->id, id);
    WS_Assert(ws);
}

实现金丝雀来检测溢出的所有麻烦都消失了,因为有了Cheri,我们可以限制 s 指针,因此在工作区之外书写是 by definition 不可能,只要您的指针派生自 s

更少的内存浪费,更强大的检查和更可读的源代码,有什么不好的呢?

当从工作区进行分配时,Cheri可以将返回的指针限制为仅指向已分配的空间:

void *
WS_Alloc(struct ws *ws, unsigned bytes)
{
    char *r;

    […]
    r = ws->f;
    ws->f += bytes;
    return(cheri_bounds_set(r, bytes));
}

Varnish字符串缓冲区

回到时间的迷雾中,达格-埃尔林·斯莫格拉夫和我设计了一个安全字符串API,名为 sbuf 适用于FreeBSD内核。

其基本思想是设置缓冲区,调用函数将文本填充到缓冲区中,这些函数执行所有繁重的工作以确保不会使缓冲区溢出。当字符串完成时,您调用一个函数来“结束”缓冲区,IF返回一个标志,该标志告诉您是否发生了溢出(或其他问题),然后您可以从另一个函数获得指向结果字符串的指针。

Varnish已经采用了sbuf的名字 vsb 。这真的不应该让任何人感到惊讶:达格-埃尔林也参与了Varnish的诞生。

很明显,在内部 vsb 几乎总是在比结果更大的缓冲区上操作,所以这是Cheri将指针缩小到一定大小的另一个明显的地方:

char *
VSB_data(const struct vsb *s)
{

    assert_VSB_integrity(s);
    assert_VSB_state(s, VSB_FINISHED);

    return (cheri_bounds_set(s->s_buf, s->s_len + 1));
}

不过,还是没有虫子。

/phk