Received: by 2002:a05:6a10:8c0a:0:0:0:0 with SMTP id go10csp725546pxb; Tue, 2 Feb 2021 16:48:48 -0800 (PST) X-Google-Smtp-Source: ABdhPJxRcM94PYMNNCnO46tX0QDfOPWrJYyVEcc8OFk9GjbdPpu/fRTzlGFbizAe91LFWatm1Fnj X-Received: by 2002:a05:6402:27cf:: with SMTP id c15mr619175ede.179.1612313328199; Tue, 02 Feb 2021 16:48:48 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1612313328; cv=none; d=google.com; s=arc-20160816; b=lmwq4UL5vXiRuKi0SV9nOpmpuaLHqdUWeCcYovH5WyjQc3XxcpcnjmcNZO81rLoloD OkvUY0Rjf/CSmOZQLbvTA0MKkF4IhogKqAq7sUkmcwP8BNaerMjXjl6kd/0na94e19j7 F0FzNQSqqvCwzFN8y6o+kTWPT0QY//qWj+qfbo0kC9QkvrqTaOJg7MArWFk9tGxyHi4H js7pyYq+ly0RYX5g+wk1uJ7OYxoedGawIvG83eVznCDQdqQmy+8dekaNFTQm+VhmPIWm n0ygiuSbEV0A9hZMewVJug157A5hNV+nserExhX0GQssD50zFTOmUc9OTphLXX4SPbYi zJvA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:cc:to:from:subject:mime-version:message-id:date :sender:dkim-signature; bh=paDnnRUZgFLJW6O0aJLXVvHItkFJZQG5OI0wx8WFRDk=; b=z9z/t5eVFzDk9sDbrY2Qo0t5UKpB1YmhWlyzFZQyXPqUPmofTpxpFtXNu57wQ+hMHq qTvtKsZiqvlsFGvhVe3Q3qwcT69F7PHrd5Wm2bA2PS4FtiMdXHHsOBi1aS45kuJSNax4 RXO4o+C6UMPHz2auUOhyVe+gXhwEMItrlC5/kOieZBxBByZYcnXhCcksAJ7bKU/VOSBC 96z8TePoe++nMCf76NPoTEn9KCWqUlT8joUfbZnqNhdFLHJdA9P5Hd0G9Y7P88JEbUGx /wRbYv5DghLg1TZlHv4NHcyKCKiaGtQhS+3Wgc9SWZCG+DBz++yTCY/Sm2CRszkb/k5n r+Iw== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@google.com header.s=20161025 header.b="Qy/q/+H2"; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=google.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [23.128.96.18]) by mx.google.com with ESMTP id lr3si334371ejb.194.2021.02.02.16.48.23; Tue, 02 Feb 2021 16:48:48 -0800 (PST) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) client-ip=23.128.96.18; Authentication-Results: mx.google.com; dkim=pass header.i=@google.com header.s=20161025 header.b="Qy/q/+H2"; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=google.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S235347AbhBBT4k (ORCPT + 99 others); Tue, 2 Feb 2021 14:56:40 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:47286 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233066AbhBBNut (ORCPT ); Tue, 2 Feb 2021 08:50:49 -0500 Received: from mail-qk1-x749.google.com (mail-qk1-x749.google.com [IPv6:2607:f8b0:4864:20::749]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 7C586C061573 for ; Tue, 2 Feb 2021 05:50:08 -0800 (PST) Received: by mail-qk1-x749.google.com with SMTP id e5so16915288qkn.2 for ; Tue, 02 Feb 2021 05:50:08 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20161025; h=sender:date:message-id:mime-version:subject:from:to:cc; bh=paDnnRUZgFLJW6O0aJLXVvHItkFJZQG5OI0wx8WFRDk=; b=Qy/q/+H2CEHwHD8Qpqz5e8CzV1q/kaW1SYbvkDYJZA19qB5/8x/cv4GBqG6x13wMVI oycMy0iqViP7WXLLwB7QSyQVboNpLfhZYnNzDD6bA9l1bDcU8RnhEoUwZ5ueCCgXK1Xt MdASwdn1NYJiamDTiGX9c90F5+Jw5P9XmZlO/OQhcTMQOaW+BULPqTFEz8IeLCr78fOk MRA8Ud4qHr08X8YBJJs3puMNEateyTJxjnfDfm5riak1dbiVnqjy1Ip8DuWtN/D7NXdE WsnpZ+d2ksNlN4lBkccTcSk3m2WN9FHY8Vsf5k7kJMVGmPRbqD/hVXlFim4J4ta1xmbn rYjQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:sender:date:message-id:mime-version:subject:from :to:cc; bh=paDnnRUZgFLJW6O0aJLXVvHItkFJZQG5OI0wx8WFRDk=; b=Eu9OlN0I9oAovjpPtGhN6D6yGpm6JjctIKeYFhLC5oeuSE+u8yADC4F2U6Tpq9t7Vq H7EkJhcTTVGfplW6bfr2y/MLgsaOBprd5seHepZYytlu5y1QHWyt2uaV6fuweOseV14y 8x/oZDOah2erk7GnKCaMYcJ8D3KnF3JA1zBb4wAQRrGV31ZXeNlTVpm+Bmd10mXtLYwn vDppUfVevdXVtQNyamwc/W05zvUEJLzeGnUqPYAkWTWbY828yKkceur2jXNyR/cl3pkN 6NYKKH9rXM6YS7GrbuMVQOaHnh7cJtDbDL50SnvhnzdkopdcGf5iKvpEhTNle45et2N5 dPJw== X-Gm-Message-State: AOAM530RjCH/JWCAo7PaPo2/d+kLVKADwJheZPtKC7t2cP/P+ghxm1aY GB/yV1THYThurB2Lr5GLtS/FqfyuFMl13w== Sender: "jackmanb via sendgmr" X-Received: from beeg.c.googlers.com ([fda3:e722:ac3:10:28:9cb1:c0a8:11db]) (user=jackmanb job=sendgmr) by 2002:a0c:c3c9:: with SMTP id p9mr20112733qvi.49.1612273807536; Tue, 02 Feb 2021 05:50:07 -0800 (PST) Date: Tue, 2 Feb 2021 13:50:02 +0000 Message-Id: <20210202135002.4024825-1-jackmanb@google.com> Mime-Version: 1.0 X-Mailer: git-send-email 2.30.0.365.g02bc693789-goog Subject: [PATCH bpf-next v3] bpf: Propagate stack bounds to registers in atomics w/ BPF_FETCH From: Brendan Jackman To: bpf@vger.kernel.org Cc: Alexei Starovoitov , Daniel Borkmann , Andrii Nakryiko , KP Singh , Florent Revest , John Fastabend , linux-kernel@vger.kernel.org, Brendan Jackman Content-Type: text/plain; charset="UTF-8" Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org When BPF_FETCH is set, atomic instructions load a value from memory into a register. The current verifier code first checks via check_mem_access whether we can access the memory, and then checks via check_reg_arg whether we can write into the register. For loads, check_reg_arg has the side-effect of marking the register's value as unkonwn, and check_mem_access has the side effect of propagating bounds from memory to the register. This currently only takes effect for stack memory. Therefore with the current order, bounds information is thrown away, but by simply reversing the order of check_reg_arg vs. check_mem_access, we can instead propagate bounds smartly. A simple test is added with an infinite loop that can only be proved unreachable if this propagation is present. This is implemented both with C and directly in test_verifier using assembly. Suggested-by: John Fastabend Signed-off-by: Brendan Jackman --- Difference from v2->v3 [1]: * Fixed missing ENABLE_ATOMICS_TESTS check. Difference from v1->v2: * Reworked commit message to clarify this only affects stack memory * Added the Suggested-by * Added a C-based test. [1]: https://lore.kernel.org/bpf/CA+i-1C2ZWUbGxWJ8kAxbri9rBboyuMaVj_BBhg+2Zf_Su9BOJA@mail.gmail.com/T/#t kernel/bpf/verifier.c | 32 +++++++++++-------- .../selftests/bpf/prog_tests/atomic_bounds.c | 15 +++++++++ .../selftests/bpf/progs/atomic_bounds.c | 24 ++++++++++++++ .../selftests/bpf/verifier/atomic_bounds.c | 27 ++++++++++++++++ 4 files changed, 84 insertions(+), 14 deletions(-) create mode 100644 tools/testing/selftests/bpf/prog_tests/atomic_bounds.c create mode 100644 tools/testing/selftests/bpf/progs/atomic_bounds.c create mode 100644 tools/testing/selftests/bpf/verifier/atomic_bounds.c diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 972fc38eb62d..5e09632efddb 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -3665,9 +3665,26 @@ static int check_atomic(struct bpf_verifier_env *env, int insn_idx, struct bpf_i return -EACCES; } + if (insn->imm & BPF_FETCH) { + if (insn->imm == BPF_CMPXCHG) + load_reg = BPF_REG_0; + else + load_reg = insn->src_reg; + + /* check and record load of old value */ + err = check_reg_arg(env, load_reg, DST_OP); + if (err) + return err; + } else { + /* This instruction accesses a memory location but doesn't + * actually load it into a register. + */ + load_reg = -1; + } + /* check whether we can read the memory */ err = check_mem_access(env, insn_idx, insn->dst_reg, insn->off, - BPF_SIZE(insn->code), BPF_READ, -1, true); + BPF_SIZE(insn->code), BPF_READ, load_reg, true); if (err) return err; @@ -3677,19 +3694,6 @@ static int check_atomic(struct bpf_verifier_env *env, int insn_idx, struct bpf_i if (err) return err; - if (!(insn->imm & BPF_FETCH)) - return 0; - - if (insn->imm == BPF_CMPXCHG) - load_reg = BPF_REG_0; - else - load_reg = insn->src_reg; - - /* check and record load of old value */ - err = check_reg_arg(env, load_reg, DST_OP); - if (err) - return err; - return 0; } diff --git a/tools/testing/selftests/bpf/prog_tests/atomic_bounds.c b/tools/testing/selftests/bpf/prog_tests/atomic_bounds.c new file mode 100644 index 000000000000..addf127068e4 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/atomic_bounds.c @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include + +#include "atomic_bounds.skel.h" + +void test_atomic_bounds(void) +{ + struct atomic_bounds *skel; + __u32 duration = 0; + + skel = atomic_bounds__open_and_load(); + if (CHECK(!skel, "skel_load", "couldn't load program\n")) + return; +} diff --git a/tools/testing/selftests/bpf/progs/atomic_bounds.c b/tools/testing/selftests/bpf/progs/atomic_bounds.c new file mode 100644 index 000000000000..e5fff7fc7f8f --- /dev/null +++ b/tools/testing/selftests/bpf/progs/atomic_bounds.c @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include + +#ifdef ENABLE_ATOMICS_TESTS +bool skip_tests __attribute((__section__(".data"))) = false; +#else +bool skip_tests = true; +#endif + +SEC("fentry/bpf_fentry_test1") +int BPF_PROG(sub, int x) +{ +#ifdef ENABLE_ATOMICS_TESTS + int a = 0; + int b = __sync_fetch_and_add(&a, 1); + /* b is certainly 0 here. Can the verifier tell? */ + while (b) + continue; +#endif + return 0; +} diff --git a/tools/testing/selftests/bpf/verifier/atomic_bounds.c b/tools/testing/selftests/bpf/verifier/atomic_bounds.c new file mode 100644 index 000000000000..e82183e4914f --- /dev/null +++ b/tools/testing/selftests/bpf/verifier/atomic_bounds.c @@ -0,0 +1,27 @@ +{ + "BPF_ATOMIC bounds propagation, mem->reg", + .insns = { + /* a = 0; */ + /* + * Note this is implemented with two separate instructions, + * where you might think one would suffice: + * + * BPF_ST_MEM(BPF_DW, BPF_REG_10, -8, 0), + * + * This is because BPF_ST_MEM doesn't seem to set the stack slot + * type to 0 when storing an immediate. + */ + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_0, -8), + /* b = atomic_fetch_add(&a, 1); */ + BPF_MOV64_IMM(BPF_REG_1, 1), + BPF_ATOMIC_OP(BPF_DW, BPF_ADD | BPF_FETCH, BPF_REG_10, BPF_REG_1, -8), + /* Verifier should be able to tell that this infinite loop isn't reachable. */ + /* if (b) while (true) continue; */ + BPF_JMP_IMM(BPF_JNE, BPF_REG_1, 0, -1), + BPF_EXIT_INSN(), + }, + .result = ACCEPT, + .result_unpriv = REJECT, + .errstr_unpriv = "back-edge", +}, base-commit: 61ca36c8c4eb3bae35a285b1ae18c514cde65439 -- 2.30.0.365.g02bc693789-goog