diff mbox series

[bpf-next,v9,7/8] bpf: lsm: Add selftests for BPF_PROG_TYPE_LSM

Message ID 20200329004356.27286-8-kpsingh@chromium.org
State Accepted
Delegated to: BPF Maintainers
Headers show
Series MAC and Audit policy using eBPF (KRSI) | expand

Commit Message

KP Singh March 29, 2020, 12:43 a.m. UTC
From: KP Singh <kpsingh@google.com>

* Load/attach a BPF program that hooks to file_mprotect (int)
  and bprm_committed_creds (void).
* Perform an action that triggers the hook.
* Verify if the audit event was received using the shared global
  variables for the process executed.
* Verify if the mprotect returns a -EPERM.

Signed-off-by: KP Singh <kpsingh@google.com>
Acked-by: Andrii Nakryiko <andriin@fb.com>
Reviewed-by: Brendan Jackman <jackmanb@google.com>
Reviewed-by: Florent Revest <revest@google.com>
Reviewed-by: Thomas Garnier <thgarnie@google.com>
Reviewed-by: James Morris <jamorris@linux.microsoft.com>
---
 tools/testing/selftests/bpf/config            |  2 +
 .../selftests/bpf/prog_tests/test_lsm.c       | 86 +++++++++++++++++++
 tools/testing/selftests/bpf/progs/lsm.c       | 48 +++++++++++
 3 files changed, 136 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/test_lsm.c
 create mode 100644 tools/testing/selftests/bpf/progs/lsm.c

Comments

Alexei Starovoitov April 2, 2020, 12:09 a.m. UTC | #1
On Sat, Mar 28, 2020 at 5:44 PM KP Singh <kpsingh@chromium.org> wrote:
> +int BPF_PROG(test_int_hook, struct vm_area_struct *vma,
> +            unsigned long reqprot, unsigned long prot, int ret)
> +{
> +       if (ret != 0)
> +               return ret;
> +
> +       __u32 pid = bpf_get_current_pid_tgid() >> 32;
> +       int is_heap = 0;
> +
> +       is_heap = (vma->vm_start >= vma->vm_mm->start_brk &&
> +                  vma->vm_end <= vma->vm_mm->brk);

This test fails for me.
I've added:
        bpf_printk("start %llx %llx\n", vma->vm_start, vma->vm_mm->start_brk);
        bpf_printk("end %llx %llx\n", vma->vm_end, vma->vm_mm->brk);
and see
cat /sys/kernel/debug/tracing/trace_pipe
            true-2285  [001] ...2   858.717432: 0: start 7f66470a2000 607000
            true-2285  [001] ...2   858.717440: 0: end 7f6647443000 607000
            true-2285  [001] ...2   858.717658: 0: start 7f6647439000 607000
            true-2285  [001] ...2   858.717659: 0: end 7f664743f000 607000
            true-2285  [001] ...2   858.717691: 0: start 605000 607000
            true-2285  [001] ...2   858.717692: 0: end 607000 607000
            true-2285  [001] ...2   858.717700: 0: start 7f6647666000 607000
            true-2285  [001] ...2   858.717701: 0: end 7f6647668000 607000
      test_progs-2283  [000] ...2   858.718030: 0: start 523000 39b9000
      test_progs-2283  [000] ...2   858.718033: 0: end 39e0000 39e0000

523000 is not >= 39b9000.
523000 is higher than vm_mm->end_data, but lower than vm_mm->start_brk.
No idea why this addr is passed into security_file_mprotect().
The address user space is passing to mprotect() is 0x39c0000 which is correct.
Could you please help debug?
KP Singh April 2, 2020, 4:03 a.m. UTC | #2
On 01-Apr 17:09, Alexei Starovoitov wrote:
> On Sat, Mar 28, 2020 at 5:44 PM KP Singh <kpsingh@chromium.org> wrote:
> > +int BPF_PROG(test_int_hook, struct vm_area_struct *vma,
> > +            unsigned long reqprot, unsigned long prot, int ret)
> > +{
> > +       if (ret != 0)
> > +               return ret;
> > +
> > +       __u32 pid = bpf_get_current_pid_tgid() >> 32;
> > +       int is_heap = 0;
> > +
> > +       is_heap = (vma->vm_start >= vma->vm_mm->start_brk &&
> > +                  vma->vm_end <= vma->vm_mm->brk);
> 
> This test fails for me.

Trying this from bpf/master:

  b9258a2cece4 ("slcan: Don't transmit uninitialized stack data in padding")

also from bpf-next/master:

 1a323ea5356e ("x86: get rid of 'errret' argument to __get_user_xyz() macross")

and I am unable to reproduce the failure (the output when using bpf/master):

 ./test_progs -t test_lsm
#70 test_lsm:OK
Summary: 1/0 PASSED, 0 SKIPPED, 0 FAILED

cat /sys/kernel/debug/tracing/trace
# tracer: nop
#
# entries-in-buffer/entries-written: 10/10   #P:4
#
#                              _-----=> irqs-off
#                             / _----=> need-resched
#                            | / _---=> hardirq/softirq
#                            || / _--=> preempt-depth
#                            ||| /     delay
#           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
#              | |       |   ||||       |         |
            true-322   [001] ...2   187.127231: 0: start 7fc7ffccc000 556a623be000
            true-322   [001] ...2   187.127238: 0: end 7fc7ffe8c000 556a623be000
            true-322   [001] ...2   187.128233: 0: start 7fc7ffe82000 556a623be000
            true-322   [001] ...2   187.128237: 0: end 7fc7ffe88000 556a623be000
            true-322   [001] ...2   187.128306: 0: start 556a604f7000 556a623be000
            true-322   [001] ...2   187.128309: 0: end 556a604f9000 556a623be000
            true-322   [001] ...2   187.128372: 0: start 7fc7ffebf000 556a623be000
            true-322   [001] ...2   187.128375: 0: end 7fc7ffec1000 556a623be000
      test_progs-321   [000] ...2   187.129952: 0: start 55dc6e8df000 55dc6e8df000
      test_progs-321   [000] ...2   187.129955: 0: end 55dc6e906000 55dc6e906000

The full run also works for me:

./test_progs

[...]

#70 test_lsm:OK
#71 test_overhead:OK
#72 tp_attach_query:OK

My config is:

  https://gist.githubusercontent.com/sinkap/cb24b955e1b6e6c1dc736054a774fb41/raw/

and the kernel commandline is (on a QEMU VM):

cat /proc/cmdline
console=ttyS0,115200 root=/dev/sda rw nokaslr

Could you share your config and cmdline?

Also, I am wondering if this happens just in the BPF program or also
in the kernel as the other variable I can think of is the compiled
bpf program itself which might be reading a different value thinking
it's vm->vma_start, possible something to do with BTF / CO RE due to a
compiler bug:

Here's the version of clang I am using:

  clang version 10.0.0 (https://github.com/llvm/llvm-project.git 2026d7b80a1a5534b5e263683c85aa95e7593b98)

- KP

> I've added:
>         bpf_printk("start %llx %llx\n", vma->vm_start, vma->vm_mm->start_brk);
>         bpf_printk("end %llx %llx\n", vma->vm_end, vma->vm_mm->brk);
> and see
> cat /sys/kernel/debug/tracing/trace_pipe
>             true-2285  [001] ...2   858.717432: 0: start 7f66470a2000 607000
>             true-2285  [001] ...2   858.717440: 0: end 7f6647443000 607000
>             true-2285  [001] ...2   858.717658: 0: start 7f6647439000 607000
>             true-2285  [001] ...2   858.717659: 0: end 7f664743f000 607000
>             true-2285  [001] ...2   858.717691: 0: start 605000 607000
>             true-2285  [001] ...2   858.717692: 0: end 607000 607000
>             true-2285  [001] ...2   858.717700: 0: start 7f6647666000 607000
>             true-2285  [001] ...2   858.717701: 0: end 7f6647668000 607000
>       test_progs-2283  [000] ...2   858.718030: 0: start 523000 39b9000
>       test_progs-2283  [000] ...2   858.718033: 0: end 39e0000 39e0000
> 
> 523000 is not >= 39b9000.
> 523000 is higher than vm_mm->end_data, but lower than vm_mm->start_brk.
> No idea why this addr is passed into security_file_mprotect().
> The address user space is passing to mprotect() is 0x39c0000 which is correct.
> Could you please help debug?
Alexei Starovoitov April 2, 2020, 4:30 a.m. UTC | #3
On Thu, Apr 02, 2020 at 06:03:57AM +0200, KP Singh wrote:
> On 01-Apr 17:09, Alexei Starovoitov wrote:
> > On Sat, Mar 28, 2020 at 5:44 PM KP Singh <kpsingh@chromium.org> wrote:
> > > +int BPF_PROG(test_int_hook, struct vm_area_struct *vma,
> > > +            unsigned long reqprot, unsigned long prot, int ret)
> > > +{
> > > +       if (ret != 0)
> > > +               return ret;
> > > +
> > > +       __u32 pid = bpf_get_current_pid_tgid() >> 32;
> > > +       int is_heap = 0;
> > > +
> > > +       is_heap = (vma->vm_start >= vma->vm_mm->start_brk &&
> > > +                  vma->vm_end <= vma->vm_mm->brk);
> > 
> > This test fails for me.
> 
> Trying this from bpf/master:
> 
>   b9258a2cece4 ("slcan: Don't transmit uninitialized stack data in padding")
> 
> also from bpf-next/master:
> 
>  1a323ea5356e ("x86: get rid of 'errret' argument to __get_user_xyz() macross")
> 
> and I am unable to reproduce the failure (the output when using bpf/master):
..
> 
> Also, I am wondering if this happens just in the BPF program or also
> in the kernel as the other variable I can think of is the compiled
> bpf program itself which might be reading a different value thinking
> it's vm->vma_start, possible something to do with BTF / CO RE due to a
> compiler bug:

I don't think it's anything to do with clang/btf or core.
I think that condition is simply incorrect.
I've added:
diff --git a/mm/mprotect.c b/mm/mprotect.c
index 311c0dadf71c..16ae0ada34ba 100644
--- a/mm/mprotect.c
+++ b/mm/mprotect.c
@@ -577,6 +577,7 @@ static int do_mprotect_pkey(unsigned long start, size_t len,
                        goto out;
                }

+               printk("start %llx %llx\n", vma->vm_start, vma->vm_mm->start_brk);
                error = security_file_mprotect(vma, reqprot, prot);

and see exactly the same values as bpf side (at least it was nice to see
that all CO-RE logic is working as expected :))

[   24.787442] start 523000 39b9000

I think it has something to do with the way test_progs is linked.
But the problem is in condition itself.
I suspect you copy-pasted it from selinux_file_mprotect() ?
I think it's incorrect there as well.
Did we just discover a way to side step selinux protection?
Try objdump -h test_progs|grep bss
the number I see in vma->vm_start is the beginning of .bss rounded to page boundary.
I wonder where your 55dc6e8df000 is coming from.
Jann Horn April 2, 2020, 5:15 a.m. UTC | #4
On Thu, Apr 2, 2020 at 6:30 AM Alexei Starovoitov
<alexei.starovoitov@gmail.com> wrote:
> On Thu, Apr 02, 2020 at 06:03:57AM +0200, KP Singh wrote:
> > On 01-Apr 17:09, Alexei Starovoitov wrote:
> > > On Sat, Mar 28, 2020 at 5:44 PM KP Singh <kpsingh@chromium.org> wrote:
> > > > +int BPF_PROG(test_int_hook, struct vm_area_struct *vma,
> > > > +            unsigned long reqprot, unsigned long prot, int ret)
> > > > +{
> > > > +       if (ret != 0)
> > > > +               return ret;
> > > > +
> > > > +       __u32 pid = bpf_get_current_pid_tgid() >> 32;
> > > > +       int is_heap = 0;
> > > > +
> > > > +       is_heap = (vma->vm_start >= vma->vm_mm->start_brk &&
> > > > +                  vma->vm_end <= vma->vm_mm->brk);
> > >
> > > This test fails for me.
> >
> > Trying this from bpf/master:
> >
> >   b9258a2cece4 ("slcan: Don't transmit uninitialized stack data in padding")
> >
> > also from bpf-next/master:
> >
> >  1a323ea5356e ("x86: get rid of 'errret' argument to __get_user_xyz() macross")
> >
> > and I am unable to reproduce the failure (the output when using bpf/master):
> ..
> >
> > Also, I am wondering if this happens just in the BPF program or also
> > in the kernel as the other variable I can think of is the compiled
> > bpf program itself which might be reading a different value thinking
> > it's vm->vma_start, possible something to do with BTF / CO RE due to a
> > compiler bug:
>
> I don't think it's anything to do with clang/btf or core.
> I think that condition is simply incorrect.
> I've added:
> diff --git a/mm/mprotect.c b/mm/mprotect.c
> index 311c0dadf71c..16ae0ada34ba 100644
> --- a/mm/mprotect.c
> +++ b/mm/mprotect.c
> @@ -577,6 +577,7 @@ static int do_mprotect_pkey(unsigned long start, size_t len,
>                         goto out;
>                 }
>
> +               printk("start %llx %llx\n", vma->vm_start, vma->vm_mm->start_brk);
>                 error = security_file_mprotect(vma, reqprot, prot);
>
> and see exactly the same values as bpf side (at least it was nice to see
> that all CO-RE logic is working as expected :))
>
> [   24.787442] start 523000 39b9000
>
> I think it has something to do with the way test_progs is linked.
> But the problem is in condition itself.
> I suspect you copy-pasted it from selinux_file_mprotect() ?
> I think it's incorrect there as well.
> Did we just discover a way to side step selinux protection?
> Try objdump -h test_progs|grep bss
> the number I see in vma->vm_start is the beginning of .bss rounded to page boundary.
> I wonder where your 55dc6e8df000 is coming from.

I suspect that you're using different versions of libc, or something's
different in the memory layout, or something like that. The brk region
is used for memory allocations using brk(), but memory allocations
using mmap() land outside it. At least some versions of libc try to
allocate memory for malloc() with brk(), then fall back to mmap() if
that fails because there's something else behind the current end of
the brk region; but I think there might also be versions of libc that
directly use mmap() and don't even try to use brk().

So yeah, security checks based on the brk region aren't exactly
useful; but e.g. in SELinux, both cases have appropriate checks. The
brk region gets SECCLASS_PROCESS:PROCESS__EXECHEAP, anonymous mmap
allocations get SECCLASS_PROCESS:PROCESS__EXECMEM in
file_map_prot_check() instead. (This makes *some* amount of sense -
although not a lot - because for the brk region you know that it comes
from something like malloc(), while an anonymous mmap() allocation
might reasonably be used for JIT executable memory.)

In other words, you may want to pick something different as test case,
since the behavior here depends on libc.
KP Singh April 2, 2020, 11:53 a.m. UTC | #5
Thanks Jann and Alexei,

On 02-Apr 07:15, Jann Horn wrote:
> On Thu, Apr 2, 2020 at 6:30 AM Alexei Starovoitov
> <alexei.starovoitov@gmail.com> wrote:
> > On Thu, Apr 02, 2020 at 06:03:57AM +0200, KP Singh wrote:
> > > On 01-Apr 17:09, Alexei Starovoitov wrote:
> > > > > +            unsigned long reqprot, unsigned long prot, int ret)
> > > > > +{

[...]

> >
> > and see exactly the same values as bpf side (at least it was nice to see
> > that all CO-RE logic is working as expected :))
> >
> > [   24.787442] start 523000 39b9000
> >
> > I think it has something to do with the way test_progs is linked.
> > But the problem is in condition itself.
> > I suspect you copy-pasted it from selinux_file_mprotect() ?
> > I think it's incorrect there as well.
> > Did we just discover a way to side step selinux protection?
> > Try objdump -h test_progs|grep bss
> > the number I see in vma->vm_start is the beginning of .bss rounded to page boundary.
> > I wonder where your 55dc6e8df000 is coming from.
> 
> I suspect that you're using different versions of libc, or something's
> different in the memory layout, or something like that. The brk region
> is used for memory allocations using brk(), but memory allocations
> using mmap() land outside it. At least some versions of libc try to
> allocate memory for malloc() with brk(), then fall back to mmap() if
> that fails because there's something else behind the current end of
> the brk region; but I think there might also be versions of libc that
> directly use mmap() and don't even try to use brk().

Yeah missed this that heap can also be allocated using mmap:

Quoting the manual:

"""
Normally, malloc() allocates memory from the heap, and adjusts the
size of the heap as required, using sbrk(2).   When  allocating blocks
of  memory larger than MMAP_THRESHOLD bytes, the glibc malloc()
implementation allocates the memory as a private anonymous mapping
using mmap(2).  MMAP_THRESHOLD is 128 kB by default, but is adjustable
using mallopt(3).  Prior to Linux  4.7  allocations performed  using
mmap(2) were unaffected by the RLIMIT_DATA resource limit; since Linux
4.7, this limit is also enforced for allocations performed using
mmap(2).
"""

So it seems like we might have separate MMAP_THRESHOLD or resource
limits.

I updated my test case to check for mmaps on the stack instead:

diff --git a/tools/testing/selftests/bpf/prog_tests/test_lsm.c b/tools/testing/selftests/bpf/prog_tests/test_lsm.c
index 1e4c258de09d..64c13c850611 100644
--- a/tools/testing/selftests/bpf/prog_tests/test_lsm.c
+++ b/tools/testing/selftests/bpf/prog_tests/test_lsm.c
@@ -15,8 +15,13 @@
 
 char *CMD_ARGS[] = {"true", NULL};
 
-int heap_mprotect(void)
+#define PAGE_SIZE 4096
+#define GET_PAGE_ADDR(ADDR)                                    \
+       (char *)(((unsigned long) ADDR) & ~(PAGE_SIZE-1))
+
+int stack_mprotect(void)
 {
+
        void *buf;
        long sz;
        int ret;
@@ -25,12 +30,9 @@ int heap_mprotect(void)
        if (sz < 0)
                return sz;
 
-       buf = memalign(sz, 2 * sz);
-       if (buf == NULL)
-               return -ENOMEM;
-
-       ret = mprotect(buf, sz, PROT_READ | PROT_WRITE | PROT_EXEC);
-       free(buf);
+       buf = alloca(PAGE_SIZE * 3);
+       ret = mprotect(GET_PAGE_ADDR(buf + PAGE_SIZE), PAGE_SIZE,
+                      PROT_READ | PROT_WRITE | PROT_EXEC);
        return ret;
 }
 
@@ -73,8 +75,8 @@ void test_test_lsm(void)
 
        skel->bss->monitored_pid = getpid();
 
-       err = heap_mprotect();
-       if (CHECK(errno != EPERM, "heap_mprotect", "want errno=EPERM, got %d\n",
+       err = stack_mprotect();
+       if (CHECK(errno != EPERM, "stack_mprotect", "want err=EPERM, got %d\n",
                  errno))
                goto close_prog;
 
diff --git a/tools/testing/selftests/bpf/progs/lsm.c b/tools/testing/selftests/bpf/progs/lsm.c
index a4e3c223028d..b4598d4bc4f7 100644
--- a/tools/testing/selftests/bpf/progs/lsm.c
+++ b/tools/testing/selftests/bpf/progs/lsm.c
@@ -23,12 +23,12 @@ int BPF_PROG(test_int_hook, struct vm_area_struct *vma,
                return ret;
 
        __u32 pid = bpf_get_current_pid_tgid() >> 32;
-       int is_heap = 0;
+       int is_stack = 0;
 
-       is_heap = (vma->vm_start >= vma->vm_mm->start_brk &&
-                  vma->vm_end <= vma->vm_mm->brk);
+       is_stack = (vma->vm_start <= vma->vm_mm->start_stack &&
+                   vma->vm_end >= vma->vm_mm->start_stack);
 
-       if (is_heap && monitored_pid == pid) {
+       if (is_stack && monitored_pid == pid) {
                mprotect_count++;
                ret = -EPERM;
        }

and the the logic seems to work for me. Do you think we could use
this instead?

- KP

> 
> So yeah, security checks based on the brk region aren't exactly
> useful; but e.g. in SELinux, both cases have appropriate checks. The
> brk region gets SECCLASS_PROCESS:PROCESS__EXECHEAP, anonymous mmap
> allocations get SECCLASS_PROCESS:PROCESS__EXECMEM in
> file_map_prot_check() instead. (This makes *some* amount of sense -
> although not a lot - because for the brk region you know that it comes
> from something like malloc(), while an anonymous mmap() allocation
> might reasonably be used for JIT executable memory.)
> 
> In other words, you may want to pick something different as test case,
> since the behavior here depends on libc.
Jann Horn April 2, 2020, 2:38 p.m. UTC | #6
On Thu, Apr 2, 2020 at 1:53 PM KP Singh <kpsingh@chromium.org> wrote:
> On 02-Apr 07:15, Jann Horn wrote:
[...]
> > I suspect that you're using different versions of libc, or something's
> > different in the memory layout, or something like that. The brk region
> > is used for memory allocations using brk(), but memory allocations
> > using mmap() land outside it. At least some versions of libc try to
> > allocate memory for malloc() with brk(), then fall back to mmap() if
> > that fails because there's something else behind the current end of
> > the brk region; but I think there might also be versions of libc that
> > directly use mmap() and don't even try to use brk().
>
> Yeah missed this that heap can also be allocated using mmap:
[...]
> I updated my test case to check for mmaps on the stack instead:
[...]
> +       is_stack = (vma->vm_start <= vma->vm_mm->start_stack &&
> +                   vma->vm_end >= vma->vm_mm->start_stack);
>
> -       if (is_heap && monitored_pid == pid) {
> +       if (is_stack && monitored_pid == pid) {
>                 mprotect_count++;
>                 ret = -EPERM;
>         }
>
> and the the logic seems to work for me. Do you think we could use
> this instead?

Yeah, I think that should work. (Just keep in mind that a successful
mprotect() operation will split the VMA into three VMAs - but that
shouldn't be a problem here, since you only do it once per process,
and since you're denying the operation, it won't go through anyway.)
KP Singh April 2, 2020, 2:40 p.m. UTC | #7
On Thu, Apr 2, 2020 at 4:39 PM Jann Horn <jannh@google.com> wrote:
>
> On Thu, Apr 2, 2020 at 1:53 PM KP Singh <kpsingh@chromium.org> wrote:
> > On 02-Apr 07:15, Jann Horn wrote:
> [...]
> > > I suspect that you're using different versions of libc, or something's
> > > different in the memory layout, or something like that. The brk region
> > > is used for memory allocations using brk(), but memory allocations
> > > using mmap() land outside it. At least some versions of libc try to
> > > allocate memory for malloc() with brk(), then fall back to mmap() if
> > > that fails because there's something else behind the current end of
> > > the brk region; but I think there might also be versions of libc that
> > > directly use mmap() and don't even try to use brk().
> >
> > Yeah missed this that heap can also be allocated using mmap:
> [...]
> > I updated my test case to check for mmaps on the stack instead:
> [...]
> > +       is_stack = (vma->vm_start <= vma->vm_mm->start_stack &&
> > +                   vma->vm_end >= vma->vm_mm->start_stack);
> >
> > -       if (is_heap && monitored_pid == pid) {
> > +       if (is_stack && monitored_pid == pid) {
> >                 mprotect_count++;
> >                 ret = -EPERM;
> >         }
> >
> > and the the logic seems to work for me. Do you think we could use
> > this instead?
>
> Yeah, I think that should work. (Just keep in mind that a successful
> mprotect() operation will split the VMA into three VMAs - but that
> shouldn't be a problem here, since you only do it once per process,
> and since you're denying the operation, it won't go through anyway.)

Okay I will send a patch that fixes this test. Thanks everyone!

- KP
Alexei Starovoitov April 2, 2020, 3:57 p.m. UTC | #8
On Thu, Apr 02, 2020 at 01:53:06PM +0200, KP Singh wrote:
>  
> -int heap_mprotect(void)
> +#define PAGE_SIZE 4096
> +#define GET_PAGE_ADDR(ADDR)                                    \
> +       (char *)(((unsigned long) ADDR) & ~(PAGE_SIZE-1))
> +
> +int stack_mprotect(void)
>  {
> +
>         void *buf;
>         long sz;
>         int ret;
> @@ -25,12 +30,9 @@ int heap_mprotect(void)
>         if (sz < 0)
>                 return sz;
>  
> -       buf = memalign(sz, 2 * sz);
> -       if (buf == NULL)
> -               return -ENOMEM;
> -
> -       ret = mprotect(buf, sz, PROT_READ | PROT_WRITE | PROT_EXEC);
> -       free(buf);
> +       buf = alloca(PAGE_SIZE * 3);
> +       ret = mprotect(GET_PAGE_ADDR(buf + PAGE_SIZE), PAGE_SIZE,
> +                      PROT_READ | PROT_WRITE | PROT_EXEC);

Great. Something like this should work.
Please use sysconf(_SC_PAGE_SIZE); like prog_tests/mmap.c does.
selftests/bpf are run not only on x86
diff mbox series

Patch

diff --git a/tools/testing/selftests/bpf/config b/tools/testing/selftests/bpf/config
index 5dc109f4c097..60e3ae5d4e48 100644
--- a/tools/testing/selftests/bpf/config
+++ b/tools/testing/selftests/bpf/config
@@ -35,3 +35,5 @@  CONFIG_MPLS_ROUTING=m
 CONFIG_MPLS_IPTUNNEL=m
 CONFIG_IPV6_SIT=m
 CONFIG_BPF_JIT=y
+CONFIG_BPF_LSM=y
+CONFIG_SECURITY=y
diff --git a/tools/testing/selftests/bpf/prog_tests/test_lsm.c b/tools/testing/selftests/bpf/prog_tests/test_lsm.c
new file mode 100644
index 000000000000..1e4c258de09d
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/test_lsm.c
@@ -0,0 +1,86 @@ 
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (C) 2020 Google LLC.
+ */
+
+#include <test_progs.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <malloc.h>
+#include <stdlib.h>
+
+#include "lsm.skel.h"
+
+char *CMD_ARGS[] = {"true", NULL};
+
+int heap_mprotect(void)
+{
+	void *buf;
+	long sz;
+	int ret;
+
+	sz = sysconf(_SC_PAGESIZE);
+	if (sz < 0)
+		return sz;
+
+	buf = memalign(sz, 2 * sz);
+	if (buf == NULL)
+		return -ENOMEM;
+
+	ret = mprotect(buf, sz, PROT_READ | PROT_WRITE | PROT_EXEC);
+	free(buf);
+	return ret;
+}
+
+int exec_cmd(int *monitored_pid)
+{
+	int child_pid, child_status;
+
+	child_pid = fork();
+	if (child_pid == 0) {
+		*monitored_pid = getpid();
+		execvp(CMD_ARGS[0], CMD_ARGS);
+		return -EINVAL;
+	} else if (child_pid > 0) {
+		waitpid(child_pid, &child_status, 0);
+		return child_status;
+	}
+
+	return -EINVAL;
+}
+
+void test_test_lsm(void)
+{
+	struct lsm *skel = NULL;
+	int err, duration = 0;
+
+	skel = lsm__open_and_load();
+	if (CHECK(!skel, "skel_load", "lsm skeleton failed\n"))
+		goto close_prog;
+
+	err = lsm__attach(skel);
+	if (CHECK(err, "attach", "lsm attach failed: %d\n", err))
+		goto close_prog;
+
+	err = exec_cmd(&skel->bss->monitored_pid);
+	if (CHECK(err < 0, "exec_cmd", "err %d errno %d\n", err, errno))
+		goto close_prog;
+
+	CHECK(skel->bss->bprm_count != 1, "bprm_count", "bprm_count = %d\n",
+	      skel->bss->bprm_count);
+
+	skel->bss->monitored_pid = getpid();
+
+	err = heap_mprotect();
+	if (CHECK(errno != EPERM, "heap_mprotect", "want errno=EPERM, got %d\n",
+		  errno))
+		goto close_prog;
+
+	CHECK(skel->bss->mprotect_count != 1, "mprotect_count",
+	      "mprotect_count = %d\n", skel->bss->mprotect_count);
+
+close_prog:
+	lsm__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/lsm.c b/tools/testing/selftests/bpf/progs/lsm.c
new file mode 100644
index 000000000000..a4e3c223028d
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/lsm.c
@@ -0,0 +1,48 @@ 
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright 2020 Google LLC.
+ */
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include  <errno.h>
+
+char _license[] SEC("license") = "GPL";
+
+int monitored_pid = 0;
+int mprotect_count = 0;
+int bprm_count = 0;
+
+SEC("lsm/file_mprotect")
+int BPF_PROG(test_int_hook, struct vm_area_struct *vma,
+	     unsigned long reqprot, unsigned long prot, int ret)
+{
+	if (ret != 0)
+		return ret;
+
+	__u32 pid = bpf_get_current_pid_tgid() >> 32;
+	int is_heap = 0;
+
+	is_heap = (vma->vm_start >= vma->vm_mm->start_brk &&
+		   vma->vm_end <= vma->vm_mm->brk);
+
+	if (is_heap && monitored_pid == pid) {
+		mprotect_count++;
+		ret = -EPERM;
+	}
+
+	return ret;
+}
+
+SEC("lsm/bprm_committed_creds")
+int BPF_PROG(test_void_hook, struct linux_binprm *bprm)
+{
+	__u32 pid = bpf_get_current_pid_tgid() >> 32;
+
+	if (monitored_pid == pid)
+		bprm_count++;
+
+	return 0;
+}