瓦尼什如何遇见切里4/N¶
那么切丽能做些什么呢?¶
Cheri可以限制指针,但不限制它们指向的内存:
#include <cheriintrin.h>
#include <stdio.h>
#include <string.h>
int
main()
{
char buf[20];
char *ptr1 = cheri_perms_and(buf, CHERI_PERM_LOAD);
char *ptr2 = buf;
strcpy(buf, "Hello World\n");
ptr1[5] = '_'; // Will core dump
ptr2[5] = '_'; // Works fine.
puts(buf);
return (0);
}
我怀疑大多数程序员会发现这是违反直觉的,因为通常情况下,写保护的是内存本身,在这种情况下,没有指针可以对其进行写入。
这就是“能力”一词的由来:指针赋予您访问内存的“能力”,因此它们可以与它们提供访问的内存分开进行限制。
如果你能从整数中创建你自己的“能力”,那不会有什么大的改进,但你不能:在Cheri下,你只能从另一个能力中创造一个新的能力,并且新的能力永远不会比它的派生能力更强大。
除了“读”和“写”权限外,能力还包括它们允许访问的一段内存的开始和长度。
在Cheri下,print f(3)图案“%#p”讲述了整个故事:
#include <cheriintrin.h>
#include <stdio.h>
#include <string.h>
int
main()
{
char buf[20];
char *ptr1 = cheri_perms_and(buf, CHERI_PERM_LOAD);
char *ptr2 = buf;
char *ptr3;
char *ptr4;
strcpy(buf, "Hello World\n");
//ptr1[5] = '_'; // Will core dump
ptr2[5] = '_'; // Works fine.
puts(buf);
printf("buf:\t%#p\n", buf);
printf("ptr1:\t%#p\n", ptr1);
printf("ptr2:\t%#p\n", ptr2);
ptr3 = ptr2 + 1;
printf("ptr3:\t%#p\n", ptr3);
ptr4 = cheri_bounds_set(ptr3, 4);
printf("ptr4:\t%#p\n", ptr4);
return (0);
}
我们得到::
buf: 0xfffffff7ff68 [rwRW,0xfffffff7ff68-0xfffffff7ff7c]
ptr1: 0xfffffff7ff68 [r,0xfffffff7ff68-0xfffffff7ff7c]
ptr2: 0xfffffff7ff68 [rwRW,0xfffffff7ff68-0xfffffff7ff7c]
ptr3: 0xfffffff7ff69 [rwRW,0xfffffff7ff68-0xfffffff7ff7c]
ptr4: 0xfffffff7ff69 [rwRW,0xfffffff7ff69-0xfffffff7ff6d]
(现在先忽略大写的‘rw’,稍后我会再讨论它们。)
因为人们在C中对指针做了一些奇怪的事情, ptr3
保持与的范围相同 ptr2
这是一个很好的借口来回答我想我的读者在这一点上会有的问题:
关于 const
?¶
C语言一团糟。
Bjarne Stroustrup介绍 const
在“带类的C语言”中,它后来变成了C++,在C++中它做了正确的事情。
ISO-C委员会的笨蛋在C89中做了一个半途而废的导入,因此它做了错误的事情:
char *strchr(const char *s, int c);
您传入一个只读指针,然后将一个读写指针返回到该字符串中?!
至少有三种不同的方式可以让他们做得对:
添加
cstrchr
功能,具有const
两边都有。使原型能够解释这一点,用一些可怕的黑客::
(const) char *strchr((const) char *s, int c);
允许同一功能的多个原型,仅在一致性上有所不同:
char *strchr(char *s, int c); const char *strchr(const char *s, int c);
但相反,他们只是忽视了这个问题,还有其他几个类似的问题。
结果是,我们开发了“const中毒”的概念,来描述这样一个事实:如果您在C源代码中使用“const”的任何程度,您最终几乎总是需要一个宏,如:
#define TRUST_ME(ptr) ((void*)(uintptr_t)(ptr))
在它不能到达的地方移走恒心。
(如果您认为这是ISO-C的杰作,问问您自己,为什么我们仍然不能显式地指定结构包装和字节顺序?几乎没有人必须分离硬件或协议文档中明确指定的数据结构,不是吗?)
使用CHERI读/写标记¶
因为 const
在C中是如此混乱,Cheri编译器不会自动从 const
函数的参数,我怀疑(但没有检查)它们可以在C++中实现的功能。
相反,我们将不得不自己做这件事,所以我在我们的 <vdef.h>
文件::
#define RO(x) cheri_perms_and((x), CHERI_PERM_LOAD)
#define ROP(x) cheri_perms_and((x), CHERI_PERM_LOAD|CHERI_PERM_LOAD_CAP)
将指针传递给 RO()
宏将其设置为只读,因此我们可以执行以下操作::
@@ -285,7 +286,7 @@ VRT_GetHdr(VRT_CTX, VCL_HEADER hs)
[…]
- return (p);
+ return (RO(p));
}
明确给予,明确给予 const
咬了一口。
两者之间的区别 RO
和 ROP
就是大写和小写的“RW”:功能有两个读/写保护级别:
你能用这个指针读写正常数据吗 (
CHERI_PERM_LOAD
)你能用这个指针读或写指针吗 (
CHERI_PERM_LOAD_CAP
)
经验法则:纯数据:只使用第一个,其中有指针的结构,两者都使用。
人们也可以用Cheri制作只写指针,但除了处理(密码)秘密的严格安全之外,只有少数几个地方可以使用它们。
现在我正在跳水 RO()
和 ROP()
进入Varnish代码,一个接一个地重新了解37个人使用了什么暴行 TRUST_ME()
躲起来。
仍然没有发现错误。
/phk