Commit 4f656638 authored by Poul-Henning Kamp's avatar Poul-Henning Kamp

Chapter 4

parent 953960e6
......@@ -43,7 +43,7 @@ ever be able to see the pointers, and those three test cases pass::
only reason it exists is precisely to to resurrect those pointers later.)
The final and really nasty test-case
====================================
------------------------------------
As previously mentioned, when you have 100K threads, you have to be
stingy with memory, in particular with the thread stacks.
......
.. _phk_cheri_4:
How Varnish met CHERI 4/N
=========================
So what can CHERI do ?
----------------------
CHERI can restrict pointers, but it does not restrict the memory
they point to::
#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);
}
I suspect most programmers will find this counter-intuitive, because
normally it is the memory itself which write-protected in which
case no pointers can ever write to it.
This is where the word "capability" comes from: The pointer is what
gives you the "capability" to access the memory, and therefore they
can be restricted separately from the memory they provide access to.
If you could just create your own "capabilities" out of integers,
that would be no big improvement, but you can not: Under CHERI you
can only make a new capability from another capability, and the new
one can never be more potent than the one it is derived from.
In addition to 'read' and 'write' permissions, capabilities contain the
start and the length of the piece of memory they allow access to.
Under CHERI the printf(3) pattern "%#p" tells the full story::
#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);
}
And we get::
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]
(Ignore the upper-case 'RW' for now, I'll get back to those later.)
Because of the odd things people do with pointers in C, ``ptr3``
keeps the same range as ``ptr2`` and that's a good excuse to
address the question I imagine my readers have at this point:
What about ``const`` ?
----------------------
The C language is a mess.
Bjarne Stroustrup introduced ``const`` in "C-with-classes",
which later became C++, and in C++ it does the right thing.
The morons on the ISO-C committee did a half-assed import
in C89, and therefore it does the wrong thing::
char *strchr(const char *s, int c);
You pass a read-only pointer in, and you get a read-write
pointer into that string back ?!
There were at least three different ways they could have done right:
* Add a ``cstrchr`` function, having ``const`` on both sides.
* Enable prototypes to explain this, with some gruesome hack::
(const) char *strchr((const) char *s, int c);
* Allow multiple prototypes for the same function, varying only in const-ness::
char *strchr(char *s, int c);
const char *strchr(const char *s, int c);
But instead they just ignored that issue, and several others like it.
The result is that we have developed the concept of "const-poisoning",
to describe the fact that if you use "const" to any extent in your
C sources, you almost always end up needing a macro like::
#define TRUST_ME(ptr) ((void*)(uintptr_t)(ptr))
To remove const-ness where it cannot go.
(If you think that is ISO-C's opus magnum, ask yourself why we still
cannot specify struct packing and endianess explicitly ? It's hardly
like anybody ever have to apart data-structures explicitly specified in
hardware or protocol documents, is it ?)
Read/Write markup with CHERI
----------------------------
Because ``const`` is such a mess in C, the CHERI compiler does not
automatically remove the write-capability from ``const`` arguments
to functions, something I suspect (but have not checked) that they
can do in C++.
Instead we will have to do it ourselves, so I have added two macros to
our ``<vdef.h>`` file::
#define RO(x) cheri_perms_and((x), CHERI_PERM_LOAD)
#define ROP(x) cheri_perms_and((x), CHERI_PERM_LOAD|CHERI_PERM_LOAD_CAP)
Passing a pointer through the ``RO()`` macro makes it read-only, so we
can do stuff like::
@@ -285,7 +286,7 @@ VRT_GetHdr(VRT_CTX, VCL_HEADER hs)
[…]
- return (p);
+ return (RO(p));
}
To explicitly give ``const`` some bite.
The difference between ``RO`` and ``ROP`` is where the upper- and
lower-case "rw" comes into the picture: Capabilities have two
levels of read/write protection:
* Can you read or write normal data with this pointer (``CHERI_PERM_LOAD``)
* Can you read or write pointers with this pointer (``CHERI_PERM_LOAD_CAP``)
Rule of thumb: Pure data: Use only the first, structs with pointers in them,
use both.
One can also make write-only pointers with CHERI, but there are
only few places where they can be gainfully employed, outside strict
security in handling of (cryptographic) secrets.
Right now I'm plunking ``RO()`` and ``ROP()`` into the varnish code,
and one by one relearning what atrocity the 37 uses of ``TRUST_ME()``
hide.
Still no bugs found.
*/phk*
......@@ -13,6 +13,7 @@ You may or may not want to know what Poul-Henning thinks.
.. toctree::
:maxdepth: 1
cheri4.rst
cheri3.rst
cheri2.rst
cheri1.rst
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment