瓦尼什如何遇见切里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 咬了一口。

两者之间的区别 ROROP 就是大写和小写的“RW”:功能有两个读/写保护级别:

  • 你能用这个指针读写正常数据吗 (CHERI_PERM_LOAD )

  • 你能用这个指针读或写指针吗 (CHERI_PERM_LOAD_CAP )

经验法则:纯数据:只使用第一个,其中有指针的结构,两者都使用。

人们也可以用Cheri制作只写指针,但除了处理(密码)秘密的严格安全之外,只有少数几个地方可以使用它们。

现在我正在跳水 RO()ROP() 进入Varnish代码,一个接一个地重新了解37个人使用了什么暴行 TRUST_ME() 躲起来。

仍然没有发现错误。

/phk