瓦尼什如何遇上切瑞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