Message ID | 20240817-mseal-depessimize-v3-7-d8d2e037df30@gmail.com |
---|---|
State | New |
Headers | show |
Series | mm: Optimize mseal checks | expand |
* Pedro Falcato <pedro.falcato@gmail.com> [240818 02:36]: > On Sat, Aug 17, 2024 at 1:18 AM Pedro Falcato <pedro.falcato@gmail.com> wrote: > > @@ -983,6 +1019,41 @@ static void test_seal_munmap_vma_with_gap(bool seal) > > REPORT_TEST_PASS(); > > } > > > > +static void test_seal_munmap_partial_across_vmas(bool seal) > > +{ > > + void *ptr; > > + unsigned long page_size = getpagesize(); > > + unsigned long size = 2 * page_size; > > + int ret; > > + int prot; > > + > > + /* > > + * Check if a partial mseal (that results in two vmas) works correctly. > > + * It might unmap the first, but it'll never unmap the second (msealed) vma. > > + */ > > Bah, obviously this comment isn't true, munmap is never partial. > I'll change this locally for v4 if there ends up being one. Besides this comment, the patch looks good. Reviewed-by: Liam R. Howlett <Liam.Howlett@Oracle.com>
Hi Pedro On Fri, Aug 16, 2024 at 5:18 PM Pedro Falcato <pedro.falcato@gmail.com> wrote: > > Add more mseal traversal tests across VMAs, where we could possibly > screw up sealing checks. These test more across-vma traversal for > mprotect, munmap and madvise. Particularly, we test for the case where a > regular VMA is followed by a sealed VMA. > > Signed-off-by: Pedro Falcato <pedro.falcato@gmail.com> > --- > tools/testing/selftests/mm/mseal_test.c | 111 +++++++++++++++++++++++++++++++- > 1 file changed, 110 insertions(+), 1 deletion(-) > > diff --git a/tools/testing/selftests/mm/mseal_test.c b/tools/testing/selftests/mm/mseal_test.c > index 259bef4945e9..0d4d40fb0f88 100644 > --- a/tools/testing/selftests/mm/mseal_test.c > +++ b/tools/testing/selftests/mm/mseal_test.c > @@ -766,6 +766,42 @@ static void test_seal_mprotect_partial_mprotect(bool seal) > REPORT_TEST_PASS(); > } > > +static void test_seal_mprotect_partial_mprotect_tail(bool seal) > +{ > + void *ptr; > + unsigned long page_size = getpagesize(); > + unsigned long size = 2 * page_size; > + int ret; > + int prot; > + > + /* > + * Check if a partial mseal (that results in two vmas) works correctly. > + * It might mprotect the first, but it'll never touch the second (msealed) vma. > + */ > + > + setup_single_address(size, &ptr); > + FAIL_TEST_IF_FALSE(ptr != (void *)-1); > + > + if (seal) { > + ret = sys_mseal(ptr + page_size, size); you are allocating 2 pages , and I assume you are sealing the second page, so the size should be page_size. ret = sys_mseal(ptr + page_size, page_size); > + FAIL_TEST_IF_FALSE(!ret); > + } > + > + ret = sys_mprotect(ptr, size, PROT_EXEC); > + if (seal) > + FAIL_TEST_IF_FALSE(ret < 0); > + else > + FAIL_TEST_IF_FALSE(!ret); > + > + if (seal) { > + FAIL_TEST_IF_FALSE(get_vma_size(ptr + page_size, &prot) > 0); > + FAIL_TEST_IF_FALSE(prot == 0x4); To test partial mprotect, the test needs to add the check for the first page to be changed, Also to avoid the merge, a PROT_NONE page can to be added in front. > + } > + > + REPORT_TEST_PASS(); > +} > + > + > static void test_seal_mprotect_two_vma_with_gap(bool seal) > { > void *ptr; > @@ -983,6 +1019,41 @@ static void test_seal_munmap_vma_with_gap(bool seal) > REPORT_TEST_PASS(); > } > > +static void test_seal_munmap_partial_across_vmas(bool seal) > +{ > + void *ptr; > + unsigned long page_size = getpagesize(); > + unsigned long size = 2 * page_size; > + int ret; > + int prot; > + > + /* > + * Check if a partial mseal (that results in two vmas) works correctly. > + * It might unmap the first, but it'll never unmap the second (msealed) vma. > + */ > + > + setup_single_address(size, &ptr); > + FAIL_TEST_IF_FALSE(ptr != (void *)-1); > + > + if (seal) { > + ret = sys_mseal(ptr + page_size, size); ret = sys_mseal(ptr + page_size, page_size); > + FAIL_TEST_IF_FALSE(!ret); > + } > + > + ret = sys_munmap(ptr, size); > + if (seal) > + FAIL_TEST_IF_FALSE(ret < 0); > + else > + FAIL_TEST_IF_FALSE(!ret); > + > + if (seal) { > + FAIL_TEST_IF_FALSE(get_vma_size(ptr + page_size, &prot) > 0); > + FAIL_TEST_IF_FALSE(prot == 0x4); To test partial unmap, the test needs to add the check for the first page to be freed, Also to avoid the merge, a PROT_NONE page needs to be in front. The test_seal_munmap_partial_across_vmas shows the behavior difference with in-loop approach and out-loop approach. Previously, both VMAs will not be freed, now the first VMA will be freed, and the second VMA (sealed) won't. This brings to the line you previously mentioned: [1] and I quote: "munmap is atomic and always has been. It's required by POSIX." So this is no longer true for the current series. Linux doesn't need to be POSIX compliant, from previous conversation on this topic with Linus [2], so I'm open to that. If this is accepted by Linus, it would be better to add comments on munmap code or tests, for future readers (in case they are curious about reasoning. ) [1] https://lore.kernel.org/linux-mm/CAKbZUD3_3KN4fAyQsD+=p3PV8svAvVyS278umX40Ehsa+zkz3w@mail.gmail.com/ [2] https://lore.kernel.org/linux-mm/CAHk-=wgDv5vPx2xoxNQh+kbvLsskWubGGGK69cqF_i4FkM-GCw@mail.gmail.com/ > + } > + > + REPORT_TEST_PASS(); > +} > + > static void test_munmap_start_freed(bool seal) > { > void *ptr; > @@ -1735,6 +1806,37 @@ static void test_seal_discard_ro_anon(bool seal) > REPORT_TEST_PASS(); > } > > +static void test_seal_discard_across_vmas(bool seal) > +{ > + void *ptr; > + unsigned long page_size = getpagesize(); > + unsigned long size = 2 * page_size; > + int ret; > + > + setup_single_address(size, &ptr); > + FAIL_TEST_IF_FALSE(ptr != (void *)-1); > + > + if (seal) { > + ret = seal_single_address(ptr + page_size, page_size); > + FAIL_TEST_IF_FALSE(!ret); > + } > + > + ret = sys_madvise(ptr, size, MADV_DONTNEED); > + if (seal) > + FAIL_TEST_IF_FALSE(ret < 0); > + else > + FAIL_TEST_IF_FALSE(!ret); > + > + ret = sys_munmap(ptr, size); > + if (seal) > + FAIL_TEST_IF_FALSE(ret < 0); > + else > + FAIL_TEST_IF_FALSE(!ret); > + > + REPORT_TEST_PASS(); > +} > + > + > static void test_seal_madvise_nodiscard(bool seal) > { > void *ptr; > @@ -1779,7 +1881,7 @@ int main(int argc, char **argv) > if (!pkey_supported()) > ksft_print_msg("PKEY not supported\n"); > > - ksft_set_plan(82); > + ksft_set_plan(88); > > test_seal_addseal(); > test_seal_unmapped_start(); > @@ -1825,12 +1927,17 @@ int main(int argc, char **argv) > test_seal_mprotect_split(false); > test_seal_mprotect_split(true); > > + test_seal_mprotect_partial_mprotect_tail(false); > + test_seal_mprotect_partial_mprotect_tail(true); > + > test_seal_munmap(false); > test_seal_munmap(true); > test_seal_munmap_two_vma(false); > test_seal_munmap_two_vma(true); > test_seal_munmap_vma_with_gap(false); > test_seal_munmap_vma_with_gap(true); > + test_seal_munmap_partial_across_vmas(false); > + test_seal_munmap_partial_across_vmas(true); > > test_munmap_start_freed(false); > test_munmap_start_freed(true); > @@ -1862,6 +1969,8 @@ int main(int argc, char **argv) > test_seal_madvise_nodiscard(true); > test_seal_discard_ro_anon(false); > test_seal_discard_ro_anon(true); > + test_seal_discard_across_vmas(false); > + test_seal_discard_across_vmas(true); > test_seal_discard_ro_anon_on_rw(false); > test_seal_discard_ro_anon_on_rw(true); > test_seal_discard_ro_anon_on_shared(false); > > -- > 2.46.0 > >
On Wed, Aug 21, 2024 at 5:27 PM Jeff Xu <jeffxu@chromium.org> wrote: > > On Wed, Aug 21, 2024 at 9:20 AM Pedro Falcato <pedro.falcato@gmail.com> wrote: > > > > On Wed, Aug 21, 2024 at 4:56 PM Jeff Xu <jeffxu@chromium.org> wrote: > > > > > > Hi Pedro > > > > > > On Fri, Aug 16, 2024 at 5:18 PM Pedro Falcato <pedro.falcato@gmail.com> wrote: > > > > > > > > Add more mseal traversal tests across VMAs, where we could possibly > > > > screw up sealing checks. These test more across-vma traversal for > > > > mprotect, munmap and madvise. Particularly, we test for the case where a > > > > regular VMA is followed by a sealed VMA. > > > > > > > > Signed-off-by: Pedro Falcato <pedro.falcato@gmail.com> > > > > --- > > > > tools/testing/selftests/mm/mseal_test.c | 111 +++++++++++++++++++++++++++++++- > > > > 1 file changed, 110 insertions(+), 1 deletion(-) > > > > > > > > diff --git a/tools/testing/selftests/mm/mseal_test.c b/tools/testing/selftests/mm/mseal_test.c > > > > index 259bef4945e9..0d4d40fb0f88 100644 > > > > --- a/tools/testing/selftests/mm/mseal_test.c > > > > +++ b/tools/testing/selftests/mm/mseal_test.c > > > > @@ -766,6 +766,42 @@ static void test_seal_mprotect_partial_mprotect(bool seal) > > > > REPORT_TEST_PASS(); > > > > } > > > > > > > > +static void test_seal_mprotect_partial_mprotect_tail(bool seal) > > > > +{ > > > > + void *ptr; > > > > + unsigned long page_size = getpagesize(); > > > > + unsigned long size = 2 * page_size; > > > > + int ret; > > > > + int prot; > > > > + > > > > + /* > > > > + * Check if a partial mseal (that results in two vmas) works correctly. > > > > + * It might mprotect the first, but it'll never touch the second (msealed) vma. > > > > + */ > > > > + > > > > + setup_single_address(size, &ptr); > > > > + FAIL_TEST_IF_FALSE(ptr != (void *)-1); > > > > + > > > > + if (seal) { > > > > + ret = sys_mseal(ptr + page_size, size); > > > you are allocating 2 pages , and I assume you are sealing the second > > > page, so the size should be page_size. > > > ret = sys_mseal(ptr + page_size, page_size); > > > > Yes, good catch, it appears to be harmless but ofc down to straight luck. > > I'll send a fixup for this and the other mistake down there. > > > > > > > > > + FAIL_TEST_IF_FALSE(!ret); > > > > + } > > > > + > > > > + ret = sys_mprotect(ptr, size, PROT_EXEC); > > > > + if (seal) > > > > + FAIL_TEST_IF_FALSE(ret < 0); > > > > + else > > > > + FAIL_TEST_IF_FALSE(!ret); > > > > + > > > > + if (seal) { > > > > + FAIL_TEST_IF_FALSE(get_vma_size(ptr + page_size, &prot) > 0); > > > > + FAIL_TEST_IF_FALSE(prot == 0x4); > > > To test partial mprotect, the test needs to add the check for the > > > first page to be changed, Also to avoid the merge, a PROT_NONE page > > > can to be added in front. > > > > No, I'm leaving partial mprotect to be undefined. It doesn't make > > sense to constraint ourselves, since POSIX wording is already loose. > > > > > > > > > + } > > > > + > > > > + REPORT_TEST_PASS(); > > > > +} > > > > + > > > > + > > > > static void test_seal_mprotect_two_vma_with_gap(bool seal) > > > > { > > > > void *ptr; > > > > @@ -983,6 +1019,41 @@ static void test_seal_munmap_vma_with_gap(bool seal) > > > > REPORT_TEST_PASS(); > > > > } > > > > > > > > +static void test_seal_munmap_partial_across_vmas(bool seal) > > > > +{ > > > > + void *ptr; > > > > + unsigned long page_size = getpagesize(); > > > > + unsigned long size = 2 * page_size; > > > > + int ret; > > > > + int prot; > > > > + > > > > + /* > > > > + * Check if a partial mseal (that results in two vmas) works correctly. > > > > + * It might unmap the first, but it'll never unmap the second (msealed) vma. > > > > + */ > > > > + > > > > + setup_single_address(size, &ptr); > > > > + FAIL_TEST_IF_FALSE(ptr != (void *)-1); > > > > + > > > > + if (seal) { > > > > + ret = sys_mseal(ptr + page_size, size); > > > ret = sys_mseal(ptr + page_size, page_size); > > > > > > > + FAIL_TEST_IF_FALSE(!ret); > > > > + } > > > > + > > > > + ret = sys_munmap(ptr, size); > > > > + if (seal) > > > > + FAIL_TEST_IF_FALSE(ret < 0); > > > > + else > > > > + FAIL_TEST_IF_FALSE(!ret); > > > > + > > > > + if (seal) { > > > > + FAIL_TEST_IF_FALSE(get_vma_size(ptr + page_size, &prot) > 0); > > > > + FAIL_TEST_IF_FALSE(prot == 0x4); > > > To test partial unmap, the test needs to add the check for the first > > > page to be freed, Also to avoid the merge, a PROT_NONE page needs to > > > be in front. > > > > I'm not testing partial unmap. Partial unmap does not happen. I have > > told you this before. > > > ok. Then this test should be as below ? (need to add PROT_NONE page > before and after) > size = get_vma_size(ptr, &prot); > FAIL_TEST_IF_FALSE(size == 2 * page_size); > FAIL_TEST_IF_FALSE(prot==0x4) That doesn't work because this region spans two vmas. I'll write something similar for the fixup. > > > > > > > > The test_seal_munmap_partial_across_vmas shows the behavior > > > difference with in-loop approach and out-loop approach. Previously, > > > both VMAs will not be freed, now the first VMA will be freed, and the > > > second VMA (sealed) won't. > > > > > > This brings to the line you previously mentioned: [1] and I quote: > > > "munmap is atomic and always has been. It's required by POSIX." > > > > This is still true, the comment was a copy-and-paste mindslip. Please > > read the email thread. It has been fixed up by Andrew. > > > Which thread/patch by Andrew ? Could you please send it to me ? (I > might missed that) This thread and this patch: https://git.kernel.org/pub/scm/linux/kernel/git/akpm/25-new.git/tree/patches/selftests-mm-add-more-mseal-traversal-tests-fix.patch
On Wed, Aug 21, 2024 at 6:28 PM Pedro Falcato <pedro.falcato@gmail.com> wrote: > > ok. Then this test should be as below ? (need to add PROT_NONE page > > before and after) > > size = get_vma_size(ptr, &prot); > > FAIL_TEST_IF_FALSE(size == 2 * page_size); > > FAIL_TEST_IF_FALSE(prot==0x4) > > That doesn't work because this region spans two vmas. I'll write > something similar for the fixup. Actually, I won't because this might cause spurious test failures on e.g !TOPDOWN mmap architectures. setup_single_address (and co) need a fresh coat of paint (wrt PROT_NONE guard regions around it) and I don't want to be the one to do it, at least not as part of this series.
Andrew, please squash this small patch with this one. It directly addresses a problem found in review. (I was told this is the preferred way to send small fixups, and I don't think anyone wants a v4 over this trivial issue) Thank you! ----8<----
diff --git a/tools/testing/selftests/mm/mseal_test.c b/tools/testing/selftests/mm/mseal_test.c index 259bef4945e9..0d4d40fb0f88 100644 --- a/tools/testing/selftests/mm/mseal_test.c +++ b/tools/testing/selftests/mm/mseal_test.c @@ -766,6 +766,42 @@ static void test_seal_mprotect_partial_mprotect(bool seal) REPORT_TEST_PASS(); } +static void test_seal_mprotect_partial_mprotect_tail(bool seal) +{ + void *ptr; + unsigned long page_size = getpagesize(); + unsigned long size = 2 * page_size; + int ret; + int prot; + + /* + * Check if a partial mseal (that results in two vmas) works correctly. + * It might mprotect the first, but it'll never touch the second (msealed) vma. + */ + + setup_single_address(size, &ptr); + FAIL_TEST_IF_FALSE(ptr != (void *)-1); + + if (seal) { + ret = sys_mseal(ptr + page_size, size); + FAIL_TEST_IF_FALSE(!ret); + } + + ret = sys_mprotect(ptr, size, PROT_EXEC); + if (seal) + FAIL_TEST_IF_FALSE(ret < 0); + else + FAIL_TEST_IF_FALSE(!ret); + + if (seal) { + FAIL_TEST_IF_FALSE(get_vma_size(ptr + page_size, &prot) > 0); + FAIL_TEST_IF_FALSE(prot == 0x4); + } + + REPORT_TEST_PASS(); +} + + static void test_seal_mprotect_two_vma_with_gap(bool seal) { void *ptr; @@ -983,6 +1019,41 @@ static void test_seal_munmap_vma_with_gap(bool seal) REPORT_TEST_PASS(); } +static void test_seal_munmap_partial_across_vmas(bool seal) +{ + void *ptr; + unsigned long page_size = getpagesize(); + unsigned long size = 2 * page_size; + int ret; + int prot; + + /* + * Check if a partial mseal (that results in two vmas) works correctly. + * It might unmap the first, but it'll never unmap the second (msealed) vma. + */ + + setup_single_address(size, &ptr); + FAIL_TEST_IF_FALSE(ptr != (void *)-1); + + if (seal) { + ret = sys_mseal(ptr + page_size, size); + FAIL_TEST_IF_FALSE(!ret); + } + + ret = sys_munmap(ptr, size); + if (seal) + FAIL_TEST_IF_FALSE(ret < 0); + else + FAIL_TEST_IF_FALSE(!ret); + + if (seal) { + FAIL_TEST_IF_FALSE(get_vma_size(ptr + page_size, &prot) > 0); + FAIL_TEST_IF_FALSE(prot == 0x4); + } + + REPORT_TEST_PASS(); +} + static void test_munmap_start_freed(bool seal) { void *ptr; @@ -1735,6 +1806,37 @@ static void test_seal_discard_ro_anon(bool seal) REPORT_TEST_PASS(); } +static void test_seal_discard_across_vmas(bool seal) +{ + void *ptr; + unsigned long page_size = getpagesize(); + unsigned long size = 2 * page_size; + int ret; + + setup_single_address(size, &ptr); + FAIL_TEST_IF_FALSE(ptr != (void *)-1); + + if (seal) { + ret = seal_single_address(ptr + page_size, page_size); + FAIL_TEST_IF_FALSE(!ret); + } + + ret = sys_madvise(ptr, size, MADV_DONTNEED); + if (seal) + FAIL_TEST_IF_FALSE(ret < 0); + else + FAIL_TEST_IF_FALSE(!ret); + + ret = sys_munmap(ptr, size); + if (seal) + FAIL_TEST_IF_FALSE(ret < 0); + else + FAIL_TEST_IF_FALSE(!ret); + + REPORT_TEST_PASS(); +} + + static void test_seal_madvise_nodiscard(bool seal) { void *ptr; @@ -1779,7 +1881,7 @@ int main(int argc, char **argv) if (!pkey_supported()) ksft_print_msg("PKEY not supported\n"); - ksft_set_plan(82); + ksft_set_plan(88); test_seal_addseal(); test_seal_unmapped_start(); @@ -1825,12 +1927,17 @@ int main(int argc, char **argv) test_seal_mprotect_split(false); test_seal_mprotect_split(true); + test_seal_mprotect_partial_mprotect_tail(false); + test_seal_mprotect_partial_mprotect_tail(true); + test_seal_munmap(false); test_seal_munmap(true); test_seal_munmap_two_vma(false); test_seal_munmap_two_vma(true); test_seal_munmap_vma_with_gap(false); test_seal_munmap_vma_with_gap(true); + test_seal_munmap_partial_across_vmas(false); + test_seal_munmap_partial_across_vmas(true); test_munmap_start_freed(false); test_munmap_start_freed(true); @@ -1862,6 +1969,8 @@ int main(int argc, char **argv) test_seal_madvise_nodiscard(true); test_seal_discard_ro_anon(false); test_seal_discard_ro_anon(true); + test_seal_discard_across_vmas(false); + test_seal_discard_across_vmas(true); test_seal_discard_ro_anon_on_rw(false); test_seal_discard_ro_anon_on_rw(true); test_seal_discard_ro_anon_on_shared(false);
Add more mseal traversal tests across VMAs, where we could possibly screw up sealing checks. These test more across-vma traversal for mprotect, munmap and madvise. Particularly, we test for the case where a regular VMA is followed by a sealed VMA. Signed-off-by: Pedro Falcato <pedro.falcato@gmail.com> --- tools/testing/selftests/mm/mseal_test.c | 111 +++++++++++++++++++++++++++++++- 1 file changed, 110 insertions(+), 1 deletion(-)