debug: test for more required cacellation points (BZ# 29274)

Message ID 20220627203745.58971-1-adhemerval.zanella@linaro.org
State Superseded
Headers
Series debug: test for more required cacellation points (BZ# 29274) |

Checks

Context Check Description
dj/TryBot-apply_patch success Patch applied to master at the time it was sent
dj/TryBot-32bit success Build for i686

Commit Message

Adhemerval Zanella June 27, 2022, 8:37 p.m. UTC
  From: Andreas Schwab <schwab@suse.de>

Generalize the test for cancellation point in __read_chk to also test
the other fortified functions with required cancellation points.

Since there is not easy way to force some syscalls to block (for
instance pread) the test tests two modes: cancellation on blocked
syscalls and early cancellation on pending request.

Checked on aarch64-linux-gnu.
---
 debug/Makefile              |   6 +-
 debug/tst-chk-cancel.c      | 263 ++++++++++++++++++++++++++++++++++++
 debug/tst-read-chk-cancel.c |  50 -------
 3 files changed, 266 insertions(+), 53 deletions(-)
 create mode 100644 debug/tst-chk-cancel.c
 delete mode 100644 debug/tst-read-chk-cancel.c
  

Comments

Adhemerval Zanella June 27, 2022, 8:42 p.m. UTC | #1
> On 27 Jun 2022, at 17:37, Adhemerval Zanella <adhemerval.zanella@linaro.org> wrote:
> 
> From: Andreas Schwab <schwab@suse.de>

I forgot to change the author, but the idea is to extend Andrea’s initial
patch to early cancellation (to test pread/pread64), add some extra tests
to check the thread is actually cancelled and cancellation handlers are
run, and add recv/recvfrom tests.

> 
> Generalize the test for cancellation point in __read_chk to also test
> the other fortified functions with required cancellation points.
> 
> Since there is not easy way to force some syscalls to block (for
> instance pread) the test tests two modes: cancellation on blocked
> syscalls and early cancellation on pending request.
> 
> Checked on aarch64-linux-gnu.
> ---
> debug/Makefile              |   6 +-
> debug/tst-chk-cancel.c      | 263 ++++++++++++++++++++++++++++++++++++
> debug/tst-read-chk-cancel.c |  50 -------
> 3 files changed, 266 insertions(+), 53 deletions(-)
> create mode 100644 debug/tst-chk-cancel.c
> delete mode 100644 debug/tst-read-chk-cancel.c
> 
> diff --git a/debug/Makefile b/debug/Makefile
> index 456b349c4d..99cdf3221b 100644
> --- a/debug/Makefile
> +++ b/debug/Makefile
> @@ -110,7 +110,7 @@ CPPFLAGS-tst-longjmp_chk2.c += -D_FORTIFY_SOURCE=1
> CFLAGS-tst-longjmp_chk3.c += -fexceptions -fasynchronous-unwind-tables
> CPPFLAGS-tst-longjmp_chk3.c += -D_FORTIFY_SOURCE=1
> CPPFLAGS-tst-realpath-chk.c += -D_FORTIFY_SOURCE=2
> -CPPFLAGS-tst-read-chk-cancel.c += -D_FORTIFY_SOURCE=2
> +CPPFLAGS-tst-chk-cancel.c += -D_FORTIFY_SOURCE=2
> 
> # _FORTIFY_SOURCE tests.
> # Auto-generate tests for _FORTIFY_SOURCE for different levels, compilers and
> @@ -206,7 +206,7 @@ tests += tst-ssp-1
> endif
> 
> ifeq ($(have-thread-library), yes)
> -tests += tst-read-chk-cancel
> +tests += tst-chk-cancel
> endif
> 
> ifeq (,$(CXX))
> @@ -248,4 +248,4 @@ $(objpfx)xtrace: xtrace.sh
> 	    -e 's|@REPORT_BUGS_TO@|$(REPORT_BUGS_TO)|' $^ > $@.new \
> 	&& rm -f $@ && mv $@.new $@ && chmod +x $@
> 
> -$(objpfx)tst-read-chk-cancel: $(shared-thread-library)
> +$(objpfx)tst-chk-cancel: $(shared-thread-library)
> diff --git a/debug/tst-chk-cancel.c b/debug/tst-chk-cancel.c
> new file mode 100644
> index 0000000000..53530db489
> --- /dev/null
> +++ b/debug/tst-chk-cancel.c
> @@ -0,0 +1,263 @@
> +/* Test for required cancellation points in fortified functions (BZ #29274)
> +   Copyright (C) 2022 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <https://www.gnu.org/licenses/>.  */
> +
> +#include <array_length.h>
> +#include <errno.h>
> +#include <poll.h>
> +#include <stdbool.h>
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <support/check.h>
> +#include <support/xthread.h>
> +#include <support/xunistd.h>
> +#include <sys/socket.h>
> +
> +/* Cleanup handling test.  */
> +static int cl_called;
> +
> +static void
> +cl (void *arg)
> +{
> +  ++cl_called;
> +}
> +
> +static int fds[2];
> +static pthread_barrier_t barrier;
> +
> +static void *
> +tf_read (void *n)
> +{
> +  xpthread_barrier_wait (&barrier);
> +
> +  if (n != (void *) 1L)
> +    xpthread_barrier_wait (&barrier);
> +
> +  pthread_cleanup_push (cl, NULL);
> +  /* This call should be forwarded to __read_chk because the buffer size
> +     is known, but the read length is non-constant.  */
> +  char c;
> +  if (read (fds[0], &c, (uintptr_t) n) != 1)
> +    return (void *) -1L;
> +
> +  pthread_cleanup_pop (0);
> +  return 0;
> +}
> +
> +static void *
> +tf_pread (void *n)
> +{
> +  xpthread_barrier_wait (&barrier);
> +
> +  if (n != (void *) 1L)
> +    xpthread_barrier_wait (&barrier);
> +
> +  pthread_cleanup_push (cl, NULL);
> +  /* This call should be forwarded to __pread_chk because the buffer size
> +     is known, but the read length is non-constant.  */
> +  char c;
> +  if (pread (fds[0], &c, (uintptr_t) n, 0) != 1)
> +    return (void *) -1L;
> +
> +  pthread_cleanup_pop (0);
> +  return 0;
> +}
> +
> +static void *
> +tf_pread64 (void *n)
> +{
> +  xpthread_barrier_wait (&barrier);
> +
> +  if (n != (void *) 1L)
> +    xpthread_barrier_wait (&barrier);
> +
> +  pthread_cleanup_push (cl, NULL);
> +  /* This call should be forwarded to __pread64_chk because the buffer size
> +     is known, but the read length is non-constant.  */
> +  char c;
> +  if (pread64 (fds[0], &c, (uintptr_t) n, 0) != 1)
> +    return (void *) -1L;
> +
> +  pthread_cleanup_pop (0);
> +  return 0;
> +}
> +
> +static void *
> +tf_poll (void *n)
> +{
> +  xpthread_barrier_wait (&barrier);
> +
> +  if (n != (void *) 1L)
> +    xpthread_barrier_wait (&barrier);
> +
> +  pthread_cleanup_push (cl, NULL);
> +  /* This call should be forwarded to __poll_chk because the pollfd size
> +     is known, but the number of entries is non-constant.  */
> +  struct pollfd pfd = { fds[0], POLLIN, 0 };
> +  if (poll (&pfd, (uintptr_t) n, -1) != 1)
> +    return (void *) -1L;
> +
> +  pthread_cleanup_pop (0);
> +  return 0;
> +}
> +
> +static void *
> +tf_ppoll (void *n)
> +{
> +  xpthread_barrier_wait (&barrier);
> +
> +  if (n != (void *) 1L)
> +    xpthread_barrier_wait (&barrier);
> +
> +  pthread_cleanup_push (cl, NULL);
> +  /* This call should be forwarded to __ppoll_chk because the pollfd size
> +     is known, but the number of entries is non-constant.  */
> +  struct pollfd pfd = { fds[0], POLLIN, 0 };
> +  if (ppoll (&pfd, (uintptr_t) n, 0, 0) != 1)
> +    return (void *) -1L;
> +
> +  pthread_cleanup_pop (0);
> +  return 0;
> +}
> +
> +static void *
> +tf_recv (void *n)
> +{
> +  xpthread_barrier_wait (&barrier);
> +
> +  if (n != (void *) 1L)
> +    xpthread_barrier_wait (&barrier);
> +
> +  pthread_cleanup_push (cl, NULL);
> +  /* This call should be forwarded to __ppoll_chk because the pollfd size
> +     is known, but the number of entries is non-constant.  */
> +  char c;
> +  if (recv (fds[0], &c, (uintptr_t) n, 0) != 1)
> +    return (void *) -1L;
> +
> +  pthread_cleanup_pop (0);
> +  return 0;
> +}
> +
> +static void *
> +tf_recvfrom (void *n)
> +{
> +  xpthread_barrier_wait (&barrier);
> +
> +  if (n != (void *) 1L)
> +    xpthread_barrier_wait (&barrier);
> +
> +  pthread_cleanup_push (cl, NULL);
> +  /* This call should be forwarded to __ppoll_chk because the pollfd size
> +     is known, but the number of entries is non-constant.  */
> +  char c;
> +  if (recvfrom (fds[0], &c, (uintptr_t) n, 0, NULL, NULL) != 1)
> +    return (void *) -1L;
> +
> +  pthread_cleanup_pop (0);
> +  return 0;
> +}
> +
> +static struct cancel_tests
> +{
> +  const char *name;
> +  void *(*tf) (void *);
> +  bool only_early;
> +#define ADD_TEST(name, early) { #name, tf_##name, early }
> +} tests[] =
> +{
> +  ADD_TEST (poll,     false),
> +  ADD_TEST (ppoll,    false),
> +  ADD_TEST (pread,    true),
> +  ADD_TEST (pread64,  true),
> +  ADD_TEST (read,     false),
> +  ADD_TEST (recv,     false),
> +  ADD_TEST (recvfrom, false),
> +};
> +
> +/* Set the send buffer of socket S to 1 byte so any send operation
> +   done with WRITE_BUFFER_SIZE bytes will force syscall blocking.  */
> +static void
> +set_socket_buffer (int s)
> +{
> +  int val = 1;
> +  socklen_t len = sizeof (val);
> +
> +  TEST_VERIFY_EXIT (setsockopt (s, SOL_SOCKET, SO_SNDBUF, &val,
> +                    sizeof (val)) == 0);
> +  TEST_VERIFY_EXIT (getsockopt (s, SOL_SOCKET, SO_SNDBUF, &val, &len) == 0);
> +  printf ("%s: got size %d\n", __func__, val);
> +}
> +
> +static int
> +do_test (void)
> +{
> +  xpthread_barrier_init (&barrier, 0, 2);
> +
> +  if (socketpair (AF_UNIX, SOCK_STREAM, 0, fds) != 0)
> +    FAIL_EXIT1 ("socketpair: %m");
> +  set_socket_buffer (fds[1]);
> +
> +  for (int i = 0; i < array_length (tests); i++)
> +    {
> +      if (tests[i].only_early)
> +	continue;
> +
> +      xpthread_barrier_init (&barrier, NULL, 2);
> +      /* Reset the counter for the cleanup handler.  */
> +      cl_called = 0;
> +
> +      pthread_t thr = xpthread_create (0, tests[i].tf, (void *) 1L);
> +      xpthread_barrier_wait (&barrier);
> +
> +      struct timespec ts = { .tv_sec = 0, .tv_nsec = 100000000 };
> +      TEMP_FAILURE_RETRY (clock_nanosleep (CLOCK_REALTIME, 0, &ts, &ts));
> +
> +      xpthread_cancel (thr);
> +
> +      void *status = xpthread_join (thr);
> +      TEST_VERIFY (status == PTHREAD_CANCELED);
> +      TEST_COMPARE (cl_called, 1);
> +
> +      printf ("in-time cancel test of '%s' successful\n", tests[i].name);
> +    }
> +
> +  for (int i = 0; i < array_length (tests); i++)
> +    {
> +      xpthread_barrier_init (&barrier, NULL, 2);
> +      /* Reset the counter for the cleanup handler.  */
> +      cl_called = 0;
> +
> +      pthread_t thr = xpthread_create (0, tests[i].tf, NULL);
> +      xpthread_barrier_wait (&barrier);
> +
> +      xpthread_cancel (thr);
> +      xpthread_barrier_wait (&barrier);
> +
> +      void *status = xpthread_join (thr);
> +      TEST_VERIFY (status == PTHREAD_CANCELED);
> +      TEST_COMPARE (cl_called, 1);
> +
> +      printf ("early cancel test of '%s' successful\n", tests[i].name);
> +    }
> +
> +  xpthread_barrier_destroy (&barrier);
> +
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>
> diff --git a/debug/tst-read-chk-cancel.c b/debug/tst-read-chk-cancel.c
> deleted file mode 100644
> index 7e06afb596..0000000000
> --- a/debug/tst-read-chk-cancel.c
> +++ /dev/null
> @@ -1,50 +0,0 @@
> -/* Test that __read_chk is a cancellation point (BZ #29274)
> -   Copyright (C) 2022 Free Software Foundation, Inc.
> -   This file is part of the GNU C Library.
> -
> -   The GNU C Library is free software; you can redistribute it and/or
> -   modify it under the terms of the GNU Lesser General Public
> -   License as published by the Free Software Foundation; either
> -   version 2.1 of the License, or (at your option) any later version.
> -
> -   The GNU C Library is distributed in the hope that it will be useful,
> -   but WITHOUT ANY WARRANTY; without even the implied warranty of
> -   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> -   Lesser General Public License for more details.
> -
> -   You should have received a copy of the GNU Lesser General Public
> -   License along with the GNU C Library; if not, see
> -   <https://www.gnu.org/licenses/>.  */
> -
> -#include <stdint.h>
> -#include <support/xunistd.h>
> -#include <support/xthread.h>
> -
> -static int pipe_fds[2];
> -static pthread_barrier_t barrier;
> -
> -static void *
> -read_thread (void *n)
> -{
> -  xpthread_barrier_wait (&barrier);
> -  char c;
> -  /* This call should be forwarded to __read_chk because the buffer size
> -     is known, but the read length is non-constant.  */
> -  if (read (pipe_fds[0], &c, (uintptr_t) n) != 1)
> -    return (void *) -1L;
> -  return 0;
> -}
> -
> -static int
> -do_test (void)
> -{
> -  xpthread_barrier_init (&barrier, 0, 2);
> -  xpipe (pipe_fds);
> -  pthread_t thr = xpthread_create (0, read_thread, (void *) 1L);
> -  xpthread_barrier_wait (&barrier);
> -  xpthread_cancel (thr);
> -  xpthread_join (thr);
> -  return 0;
> -}
> -
> -#include <support/test-driver.c>
> -- 
> 2.34.1
>
  
Carlos O'Donell July 11, 2022, 2:38 p.m. UTC | #2
On 6/27/22 16:37, Adhemerval Zanella via Libc-alpha wrote:
> From: Andreas Schwab <schwab@suse.de>

Please send a v2 with Coauthored-by:

Please review the concurrency aspects I have noted.
 
> Generalize the test for cancellation point in __read_chk to also test
> the other fortified functions with required cancellation points.
> 
> Since there is not easy way to force some syscalls to block (for
> instance pread) the test tests two modes: cancellation on blocked
> syscalls and early cancellation on pending request.

Right. Either the cancel is received just before or just after. There is a third
test which is just after the call returns and the side-effect is visible.

> 
> Checked on aarch64-linux-gnu.
> ---
>  debug/Makefile              |   6 +-
>  debug/tst-chk-cancel.c      | 263 ++++++++++++++++++++++++++++++++++++
>  debug/tst-read-chk-cancel.c |  50 -------
>  3 files changed, 266 insertions(+), 53 deletions(-)
>  create mode 100644 debug/tst-chk-cancel.c
>  delete mode 100644 debug/tst-read-chk-cancel.c
> 
> diff --git a/debug/Makefile b/debug/Makefile
> index 456b349c4d..99cdf3221b 100644
> --- a/debug/Makefile
> +++ b/debug/Makefile
> @@ -110,7 +110,7 @@ CPPFLAGS-tst-longjmp_chk2.c += -D_FORTIFY_SOURCE=1
>  CFLAGS-tst-longjmp_chk3.c += -fexceptions -fasynchronous-unwind-tables
>  CPPFLAGS-tst-longjmp_chk3.c += -D_FORTIFY_SOURCE=1
>  CPPFLAGS-tst-realpath-chk.c += -D_FORTIFY_SOURCE=2
> -CPPFLAGS-tst-read-chk-cancel.c += -D_FORTIFY_SOURCE=2
> +CPPFLAGS-tst-chk-cancel.c += -D_FORTIFY_SOURCE=2

OK.

>  
>  # _FORTIFY_SOURCE tests.
>  # Auto-generate tests for _FORTIFY_SOURCE for different levels, compilers and
> @@ -206,7 +206,7 @@ tests += tst-ssp-1
>  endif
>  
>  ifeq ($(have-thread-library), yes)
> -tests += tst-read-chk-cancel
> +tests += tst-chk-cancel

OK.

>  endif
>  
>  ifeq (,$(CXX))
> @@ -248,4 +248,4 @@ $(objpfx)xtrace: xtrace.sh
>  	    -e 's|@REPORT_BUGS_TO@|$(REPORT_BUGS_TO)|' $^ > $@.new \
>  	&& rm -f $@ && mv $@.new $@ && chmod +x $@
>  
> -$(objpfx)tst-read-chk-cancel: $(shared-thread-library)
> +$(objpfx)tst-chk-cancel: $(shared-thread-library)

OK.

> diff --git a/debug/tst-chk-cancel.c b/debug/tst-chk-cancel.c
> new file mode 100644
> index 0000000000..53530db489
> --- /dev/null
> +++ b/debug/tst-chk-cancel.c
> @@ -0,0 +1,263 @@
> +/* Test for required cancellation points in fortified functions (BZ #29274)
> +   Copyright (C) 2022 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <https://www.gnu.org/licenses/>.  */
> +
> +#include <array_length.h>
> +#include <errno.h>
> +#include <poll.h>
> +#include <stdbool.h>
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <support/check.h>
> +#include <support/xthread.h>
> +#include <support/xunistd.h>
> +#include <sys/socket.h>
> +
> +/* Cleanup handling test.  */
> +static int cl_called;
> +
> +static void
> +cl (void *arg)
> +{
> +  ++cl_called;
> +}
> +
> +static int fds[2];
> +static pthread_barrier_t barrier;
> +
> +static void *
> +tf_read (void *n)
> +{
> +  xpthread_barrier_wait (&barrier);
> +
> +  if (n != (void *) 1L)
> +    xpthread_barrier_wait (&barrier);
> +
> +  pthread_cleanup_push (cl, NULL);
> +  /* This call should be forwarded to __read_chk because the buffer size
> +     is known, but the read length is non-constant.  */
> +  char c;
> +  if (read (fds[0], &c, (uintptr_t) n) != 1)
> +    return (void *) -1L;
> +
> +  pthread_cleanup_pop (0);
> +  return 0;
> +}

OK. read test with cancellation.

> +
> +static void *
> +tf_pread (void *n)
> +{
> +  xpthread_barrier_wait (&barrier);
> +
> +  if (n != (void *) 1L)
> +    xpthread_barrier_wait (&barrier);
> +
> +  pthread_cleanup_push (cl, NULL);
> +  /* This call should be forwarded to __pread_chk because the buffer size
> +     is known, but the read length is non-constant.  */
> +  char c;
> +  if (pread (fds[0], &c, (uintptr_t) n, 0) != 1)
> +    return (void *) -1L;
> +
> +  pthread_cleanup_pop (0);
> +  return 0;
> +}

OK. pread test with cancel.

> +
> +static void *
> +tf_pread64 (void *n)
> +{
> +  xpthread_barrier_wait (&barrier);
> +
> +  if (n != (void *) 1L)
> +    xpthread_barrier_wait (&barrier);
> +
> +  pthread_cleanup_push (cl, NULL);
> +  /* This call should be forwarded to __pread64_chk because the buffer size
> +     is known, but the read length is non-constant.  */
> +  char c;
> +  if (pread64 (fds[0], &c, (uintptr_t) n, 0) != 1)
> +    return (void *) -1L;
> +
> +  pthread_cleanup_pop (0);
> +  return 0;
> +}

OK. pread64 test.

> +
> +static void *
> +tf_poll (void *n)
> +{
> +  xpthread_barrier_wait (&barrier);
> +
> +  if (n != (void *) 1L)
> +    xpthread_barrier_wait (&barrier);
> +
> +  pthread_cleanup_push (cl, NULL);
> +  /* This call should be forwarded to __poll_chk because the pollfd size
> +     is known, but the number of entries is non-constant.  */
> +  struct pollfd pfd = { fds[0], POLLIN, 0 };
> +  if (poll (&pfd, (uintptr_t) n, -1) != 1)
> +    return (void *) -1L;
> +
> +  pthread_cleanup_pop (0);
> +  return 0;
> +}
> +

OK. poll test.

> +static void *
> +tf_ppoll (void *n)
> +{
> +  xpthread_barrier_wait (&barrier);
> +
> +  if (n != (void *) 1L)
> +    xpthread_barrier_wait (&barrier);
> +
> +  pthread_cleanup_push (cl, NULL);
> +  /* This call should be forwarded to __ppoll_chk because the pollfd size
> +     is known, but the number of entries is non-constant.  */
> +  struct pollfd pfd = { fds[0], POLLIN, 0 };
> +  if (ppoll (&pfd, (uintptr_t) n, 0, 0) != 1)
> +    return (void *) -1L;
> +
> +  pthread_cleanup_pop (0);
> +  return 0;
> +}

OK. ppoll test.

> +
> +static void *
> +tf_recv (void *n)
> +{
> +  xpthread_barrier_wait (&barrier);
> +
> +  if (n != (void *) 1L)
> +    xpthread_barrier_wait (&barrier);
> +
> +  pthread_cleanup_push (cl, NULL);
> +  /* This call should be forwarded to __ppoll_chk because the pollfd size
> +     is known, but the number of entries is non-constant.  */
> +  char c;
> +  if (recv (fds[0], &c, (uintptr_t) n, 0) != 1)
> +    return (void *) -1L;
> +
> +  pthread_cleanup_pop (0);
> +  return 0;
> +}

OK.

> +
> +static void *
> +tf_recvfrom (void *n)
> +{
> +  xpthread_barrier_wait (&barrier);
> +
> +  if (n != (void *) 1L)
> +    xpthread_barrier_wait (&barrier);
> +
> +  pthread_cleanup_push (cl, NULL);
> +  /* This call should be forwarded to __ppoll_chk because the pollfd size
> +     is known, but the number of entries is non-constant.  */
> +  char c;
> +  if (recvfrom (fds[0], &c, (uintptr_t) n, 0, NULL, NULL) != 1)
> +    return (void *) -1L;
> +
> +  pthread_cleanup_pop (0);
> +  return 0;
> +}

OK.

> +
> +static struct cancel_tests
> +{
> +  const char *name;
> +  void *(*tf) (void *);
> +  bool only_early;
> +#define ADD_TEST(name, early) { #name, tf_##name, early }
> +} tests[] =
> +{
> +  ADD_TEST (poll,     false),
> +  ADD_TEST (ppoll,    false),
> +  ADD_TEST (pread,    true),
> +  ADD_TEST (pread64,  true),
> +  ADD_TEST (read,     false),
> +  ADD_TEST (recv,     false),
> +  ADD_TEST (recvfrom, false),
> +};
> +
> +/* Set the send buffer of socket S to 1 byte so any send operation
> +   done with WRITE_BUFFER_SIZE bytes will force syscall blocking.  */
> +static void
> +set_socket_buffer (int s)
> +{
> +  int val = 1;
> +  socklen_t len = sizeof (val);
> +
> +  TEST_VERIFY_EXIT (setsockopt (s, SOL_SOCKET, SO_SNDBUF, &val,
> +                    sizeof (val)) == 0);
> +  TEST_VERIFY_EXIT (getsockopt (s, SOL_SOCKET, SO_SNDBUF, &val, &len) == 0);
> +  printf ("%s: got size %d\n", __func__, val);

OK.

> +}
> +
> +static int
> +do_test (void)
> +{
> +  xpthread_barrier_init (&barrier, 0, 2);

OK. Needs two threads.

> +
> +  if (socketpair (AF_UNIX, SOCK_STREAM, 0, fds) != 0)
> +    FAIL_EXIT1 ("socketpair: %m");
> +  set_socket_buffer (fds[1]);
> +
> +  for (int i = 0; i < array_length (tests); i++)
> +    {
> +      if (tests[i].only_early)
> +	continue;
> +
> +      xpthread_barrier_init (&barrier, NULL, 2);

OK. Redundant in some cases, but OK because of the loops use.

> +      /* Reset the counter for the cleanup handler.  */
> +      cl_called = 0;
> +
> +      pthread_t thr = xpthread_create (0, tests[i].tf, (void *) 1L);

           /* After this wait the threads cancellation hanler is installed.  */

> +      xpthread_barrier_wait (&barrier);
> +
> +      struct timespec ts = { .tv_sec = 0, .tv_nsec = 100000000 };
> +      TEMP_FAILURE_RETRY (clock_nanosleep (CLOCK_REALTIME, 0, &ts, &ts));

This can spuriously fail under load.

Consider the ordering:

Thread A:
  xpthread_barrier_wait (&barrier);
  ... scheduled away.

Thread Main:
  xpthread_cancel (thr);
  void *status = xpthread_join (thr);
  TEST_VERIFY (status == PTHREAD_CANCELED);
  TEST_COMPARE (cl_called, 1);
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cl_called still zero because 100ms is not enough time.
  
The fix for this is to adjust tf_read (and all the others) to barrier wait *after*
the cancellation function has been put in place.

e.g.

static void *
tf_pread (void *n)
{
  pthread_cleanup_push (cl, NULL);

  xpthread_barrier_wait (&barrier);

  /* Wait as close to the syscall as possible.  */
  if (n != (void *) 1L)
    xpthread_barrier_wait (&barrier);

  /* This call should be forwarded to __pread_chk because the buffer size
     is known, but the read length is non-constant.  */
  char c;
  if (pread (fds[0], &c, (uintptr_t) n, 0) != 1)
    return (void *) -1L;

  pthread_cleanup_pop (0);
  return 0;
}

This would guarantee that Thread Main upon resumption from the wait must
be able to observe the pthread_cleanup_push in the other thread.

> +
> +      xpthread_cancel (thr);
> +
> +      void *status = xpthread_join (thr);
> +      TEST_VERIFY (status == PTHREAD_CANCELED);
> +      TEST_COMPARE (cl_called, 1);
> +
> +      printf ("in-time cancel test of '%s' successful\n", tests[i].name);

The wait allows us to get to the syscall if possible.

> +    }
> +
> +  for (int i = 0; i < array_length (tests); i++)
> +    {
> +      xpthread_barrier_init (&barrier, NULL, 2);
> +      /* Reset the counter for the cleanup handler.  */
> +      cl_called = 0;
> +
> +      pthread_t thr = xpthread_create (0, tests[i].tf, NULL);

           /* After this wait the cancellation handler is in place.  */

> +      xpthread_barrier_wait (&barrier);
> +
> +      xpthread_cancel (thr);
> +      xpthread_barrier_wait (&barrier);
  
OK. We are guarnateed the handler is in place. We will *not* see the syscall called
because of the second barrier.

> +
> +      void *status = xpthread_join (thr);
> +      TEST_VERIFY (status == PTHREAD_CANCELED);
> +      TEST_COMPARE (cl_called, 1);

OK.

> +
> +      printf ("early cancel test of '%s' successful\n", tests[i].name);

OK.

> +    }
> +
> +  xpthread_barrier_destroy (&barrier);
> +
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>
> diff --git a/debug/tst-read-chk-cancel.c b/debug/tst-read-chk-cancel.c
> deleted file mode 100644
> index 7e06afb596..0000000000
> --- a/debug/tst-read-chk-cancel.c
> +++ /dev/null
> @@ -1,50 +0,0 @@
> -/* Test that __read_chk is a cancellation point (BZ #29274)
> -   Copyright (C) 2022 Free Software Foundation, Inc.
> -   This file is part of the GNU C Library.
> -
> -   The GNU C Library is free software; you can redistribute it and/or
> -   modify it under the terms of the GNU Lesser General Public
> -   License as published by the Free Software Foundation; either
> -   version 2.1 of the License, or (at your option) any later version.
> -
> -   The GNU C Library is distributed in the hope that it will be useful,
> -   but WITHOUT ANY WARRANTY; without even the implied warranty of
> -   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> -   Lesser General Public License for more details.
> -
> -   You should have received a copy of the GNU Lesser General Public
> -   License along with the GNU C Library; if not, see
> -   <https://www.gnu.org/licenses/>.  */
> -
> -#include <stdint.h>
> -#include <support/xunistd.h>
> -#include <support/xthread.h>
> -
> -static int pipe_fds[2];
> -static pthread_barrier_t barrier;
> -
> -static void *
> -read_thread (void *n)
> -{
> -  xpthread_barrier_wait (&barrier);
> -  char c;
> -  /* This call should be forwarded to __read_chk because the buffer size
> -     is known, but the read length is non-constant.  */
> -  if (read (pipe_fds[0], &c, (uintptr_t) n) != 1)
> -    return (void *) -1L;
> -  return 0;
> -}
> -
> -static int
> -do_test (void)
> -{
> -  xpthread_barrier_init (&barrier, 0, 2);
> -  xpipe (pipe_fds);
> -  pthread_t thr = xpthread_create (0, read_thread, (void *) 1L);
> -  xpthread_barrier_wait (&barrier);
> -  xpthread_cancel (thr);
> -  xpthread_join (thr);
> -  return 0;
> -}
> -
> -#include <support/test-driver.c>
  
Adhemerval Zanella July 11, 2022, 4:51 p.m. UTC | #3
> On 11 Jul 2022, at 11:38, Carlos O'Donell <carlos@redhat.com> wrote:
> 
> On 6/27/22 16:37, Adhemerval Zanella via Libc-alpha wrote:
>> From: Andreas Schwab <schwab@suse.de>
> 
> Please send a v2 with Coauthored-by:
> 
> Please review the concurrency aspects I have noted.
> 
>> Generalize the test for cancellation point in __read_chk to also test
>> the other fortified functions with required cancellation points.
>> 
>> Since there is not easy way to force some syscalls to block (for
>> instance pread) the test tests two modes: cancellation on blocked
>> syscalls and early cancellation on pending request.
> 
> Right. Either the cancel is received just before or just after. There is a third
> test which is just after the call returns and the side-effect is visible.
> 
>> 
>> Checked on aarch64-linux-gnu.
>> ---
>> debug/Makefile | 6 +-
>> debug/tst-chk-cancel.c | 263 ++++++++++++++++++++++++++++++++++++
>> debug/tst-read-chk-cancel.c | 50 -------
>> 3 files changed, 266 insertions(+), 53 deletions(-)
>> create mode 100644 debug/tst-chk-cancel.c
>> delete mode 100644 debug/tst-read-chk-cancel.c
>> 
>> diff --git a/debug/Makefile b/debug/Makefile
>> index 456b349c4d..99cdf3221b 100644
>> --- a/debug/Makefile
>> +++ b/debug/Makefile
>> @@ -110,7 +110,7 @@ CPPFLAGS-tst-longjmp_chk2.c += -D_FORTIFY_SOURCE=1
>> CFLAGS-tst-longjmp_chk3.c += -fexceptions -fasynchronous-unwind-tables
>> CPPFLAGS-tst-longjmp_chk3.c += -D_FORTIFY_SOURCE=1
>> CPPFLAGS-tst-realpath-chk.c += -D_FORTIFY_SOURCE=2
>> -CPPFLAGS-tst-read-chk-cancel.c += -D_FORTIFY_SOURCE=2
>> +CPPFLAGS-tst-chk-cancel.c += -D_FORTIFY_SOURCE=2
> 
> OK.
> 
>> 
>> # _FORTIFY_SOURCE tests.
>> # Auto-generate tests for _FORTIFY_SOURCE for different levels, compilers and
>> @@ -206,7 +206,7 @@ tests += tst-ssp-1
>> endif
>> 
>> ifeq ($(have-thread-library), yes)
>> -tests += tst-read-chk-cancel
>> +tests += tst-chk-cancel
> 
> OK.
> 
>> endif
>> 
>> ifeq (,$(CXX))
>> @@ -248,4 +248,4 @@ $(objpfx)xtrace: xtrace.sh
>> 	 -e 's|@REPORT_BUGS_TO@|$(REPORT_BUGS_TO)|' $^ > $@.new \
>> 	&& rm -f $@ && mv $@.new $@ && chmod +x $@
>> 
>> -$(objpfx)tst-read-chk-cancel: $(shared-thread-library)
>> +$(objpfx)tst-chk-cancel: $(shared-thread-library)
> 
> OK.
> 
>> diff --git a/debug/tst-chk-cancel.c b/debug/tst-chk-cancel.c
>> new file mode 100644
>> index 0000000000..53530db489
>> --- /dev/null
>> +++ b/debug/tst-chk-cancel.c
>> @@ -0,0 +1,263 @@
>> +/* Test for required cancellation points in fortified functions (BZ #29274)
>> + Copyright (C) 2022 Free Software Foundation, Inc.
>> + This file is part of the GNU C Library.
>> +
>> + The GNU C Library is free software; you can redistribute it and/or
>> + modify it under the terms of the GNU Lesser General Public
>> + License as published by the Free Software Foundation; either
>> + version 2.1 of the License, or (at your option) any later version.
>> +
>> + The GNU C Library is distributed in the hope that it will be useful,
>> + but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
>> + Lesser General Public License for more details.
>> +
>> + You should have received a copy of the GNU Lesser General Public
>> + License along with the GNU C Library; if not, see
>> + <https://www.gnu.org/licenses/>. */
>> +
>> +#include <array_length.h>
>> +#include <errno.h>
>> +#include <poll.h>
>> +#include <stdbool.h>
>> +#include <stdint.h>
>> +#include <stdio.h>
>> +#include <support/check.h>
>> +#include <support/xthread.h>
>> +#include <support/xunistd.h>
>> +#include <sys/socket.h>
>> +
>> +/* Cleanup handling test. */
>> +static int cl_called;
>> +
>> +static void
>> +cl (void *arg)
>> +{
>> + ++cl_called;
>> +}
>> +
>> +static int fds[2];
>> +static pthread_barrier_t barrier;
>> +
>> +static void *
>> +tf_read (void *n)
>> +{
>> + xpthread_barrier_wait (&barrier);
>> +
>> + if (n != (void *) 1L)
>> + xpthread_barrier_wait (&barrier);
>> +
>> + pthread_cleanup_push (cl, NULL);
>> + /* This call should be forwarded to __read_chk because the buffer size
>> + is known, but the read length is non-constant. */
>> + char c;
>> + if (read (fds[0], &c, (uintptr_t) n) != 1)
>> + return (void *) -1L;
>> +
>> + pthread_cleanup_pop (0);
>> + return 0;
>> +}
> 
> OK. read test with cancellation.
> 
>> +
>> +static void *
>> +tf_pread (void *n)
>> +{
>> + xpthread_barrier_wait (&barrier);
>> +
>> + if (n != (void *) 1L)
>> + xpthread_barrier_wait (&barrier);
>> +
>> + pthread_cleanup_push (cl, NULL);
>> + /* This call should be forwarded to __pread_chk because the buffer size
>> + is known, but the read length is non-constant. */
>> + char c;
>> + if (pread (fds[0], &c, (uintptr_t) n, 0) != 1)
>> + return (void *) -1L;
>> +
>> + pthread_cleanup_pop (0);
>> + return 0;
>> +}
> 
> OK. pread test with cancel.
> 
>> +
>> +static void *
>> +tf_pread64 (void *n)
>> +{
>> + xpthread_barrier_wait (&barrier);
>> +
>> + if (n != (void *) 1L)
>> + xpthread_barrier_wait (&barrier);
>> +
>> + pthread_cleanup_push (cl, NULL);
>> + /* This call should be forwarded to __pread64_chk because the buffer size
>> + is known, but the read length is non-constant. */
>> + char c;
>> + if (pread64 (fds[0], &c, (uintptr_t) n, 0) != 1)
>> + return (void *) -1L;
>> +
>> + pthread_cleanup_pop (0);
>> + return 0;
>> +}
> 
> OK. pread64 test.
> 
>> +
>> +static void *
>> +tf_poll (void *n)
>> +{
>> + xpthread_barrier_wait (&barrier);
>> +
>> + if (n != (void *) 1L)
>> + xpthread_barrier_wait (&barrier);
>> +
>> + pthread_cleanup_push (cl, NULL);
>> + /* This call should be forwarded to __poll_chk because the pollfd size
>> + is known, but the number of entries is non-constant. */
>> + struct pollfd pfd = { fds[0], POLLIN, 0 };
>> + if (poll (&pfd, (uintptr_t) n, -1) != 1)
>> + return (void *) -1L;
>> +
>> + pthread_cleanup_pop (0);
>> + return 0;
>> +}
>> +
> 
> OK. poll test.
> 
>> +static void *
>> +tf_ppoll (void *n)
>> +{
>> + xpthread_barrier_wait (&barrier);
>> +
>> + if (n != (void *) 1L)
>> + xpthread_barrier_wait (&barrier);
>> +
>> + pthread_cleanup_push (cl, NULL);
>> + /* This call should be forwarded to __ppoll_chk because the pollfd size
>> + is known, but the number of entries is non-constant. */
>> + struct pollfd pfd = { fds[0], POLLIN, 0 };
>> + if (ppoll (&pfd, (uintptr_t) n, 0, 0) != 1)
>> + return (void *) -1L;
>> +
>> + pthread_cleanup_pop (0);
>> + return 0;
>> +}
> 
> OK. ppoll test.
> 
>> +
>> +static void *
>> +tf_recv (void *n)
>> +{
>> + xpthread_barrier_wait (&barrier);
>> +
>> + if (n != (void *) 1L)
>> + xpthread_barrier_wait (&barrier);
>> +
>> + pthread_cleanup_push (cl, NULL);
>> + /* This call should be forwarded to __ppoll_chk because the pollfd size
>> + is known, but the number of entries is non-constant. */
>> + char c;
>> + if (recv (fds[0], &c, (uintptr_t) n, 0) != 1)
>> + return (void *) -1L;
>> +
>> + pthread_cleanup_pop (0);
>> + return 0;
>> +}
> 
> OK.
> 
>> +
>> +static void *
>> +tf_recvfrom (void *n)
>> +{
>> + xpthread_barrier_wait (&barrier);
>> +
>> + if (n != (void *) 1L)
>> + xpthread_barrier_wait (&barrier);
>> +
>> + pthread_cleanup_push (cl, NULL);
>> + /* This call should be forwarded to __ppoll_chk because the pollfd size
>> + is known, but the number of entries is non-constant. */
>> + char c;
>> + if (recvfrom (fds[0], &c, (uintptr_t) n, 0, NULL, NULL) != 1)
>> + return (void *) -1L;
>> +
>> + pthread_cleanup_pop (0);
>> + return 0;
>> +}
> 
> OK.
> 
>> +
>> +static struct cancel_tests
>> +{
>> + const char *name;
>> + void *(*tf) (void *);
>> + bool only_early;
>> +#define ADD_TEST(name, early) { #name, tf_##name, early }
>> +} tests[] =
>> +{
>> + ADD_TEST (poll, false),
>> + ADD_TEST (ppoll, false),
>> + ADD_TEST (pread, true),
>> + ADD_TEST (pread64, true),
>> + ADD_TEST (read, false),
>> + ADD_TEST (recv, false),
>> + ADD_TEST (recvfrom, false),
>> +};
>> +
>> +/* Set the send buffer of socket S to 1 byte so any send operation
>> + done with WRITE_BUFFER_SIZE bytes will force syscall blocking. */
>> +static void
>> +set_socket_buffer (int s)
>> +{
>> + int val = 1;
>> + socklen_t len = sizeof (val);
>> +
>> + TEST_VERIFY_EXIT (setsockopt (s, SOL_SOCKET, SO_SNDBUF, &val,
>> + sizeof (val)) == 0);
>> + TEST_VERIFY_EXIT (getsockopt (s, SOL_SOCKET, SO_SNDBUF, &val, &len) == 0);
>> + printf ("%s: got size %d\n", __func__, val);
> 
> OK.
> 
>> +}
>> +
>> +static int
>> +do_test (void)
>> +{
>> + xpthread_barrier_init (&barrier, 0, 2);
> 
> OK. Needs two threads.
> 
>> +
>> + if (socketpair (AF_UNIX, SOCK_STREAM, 0, fds) != 0)
>> + FAIL_EXIT1 ("socketpair: %m");
>> + set_socket_buffer (fds[1]);
>> +
>> + for (int i = 0; i < array_length (tests); i++)
>> + {
>> + if (tests[i].only_early)
>> +	continue;
>> +
>> + xpthread_barrier_init (&barrier, NULL, 2);
> 
> OK. Redundant in some cases, but OK because of the loops use.
> 
>> + /* Reset the counter for the cleanup handler. */
>> + cl_called = 0;
>> +
>> + pthread_t thr = xpthread_create (0, tests[i].tf, (void *) 1L);
> 
> /* After this wait the threads cancellation hanler is installed. */

Ack.

> 
>> + xpthread_barrier_wait (&barrier);
>> +
>> + struct timespec ts = { .tv_sec = 0, .tv_nsec = 100000000 };
>> + TEMP_FAILURE_RETRY (clock_nanosleep (CLOCK_REALTIME, 0, &ts, &ts));
> 
> This can spuriously fail under load.
> 
> Consider the ordering:
> 
> Thread A:
> xpthread_barrier_wait (&barrier);
> ... scheduled away.
> 
> Thread Main:
> xpthread_cancel (thr);
> void *status = xpthread_join (thr);
> TEST_VERIFY (status == PTHREAD_CANCELED);
> TEST_COMPARE (cl_called, 1);
> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cl_called still zero because 100ms is not enough time.
> 
> The fix for this is to adjust tf_read (and all the others) to barrier wait *after*
> the cancellation function has been put in place.
> 
> e.g.
> 
> static void *
> tf_pread (void *n)
> {
> pthread_cleanup_push (cl, NULL);
> 
> xpthread_barrier_wait (&barrier);
> 
> /* Wait as close to the syscall as possible. */
> if (n != (void *) 1L)
> xpthread_barrier_wait (&barrier);
> 
> /* This call should be forwarded to __pread_chk because the buffer size
> is known, but the read length is non-constant. */
> char c;
> if (pread (fds[0], &c, (uintptr_t) n, 0) != 1)
> return (void *) -1L;
> 
> pthread_cleanup_pop (0);
> return 0;
> }
> 
> This would guarantee that Thread Main upon resumption from the wait must
> be able to observe the pthread_cleanup_push in the other thread.

Ok, it makes sense.

> 
>> +
>> + xpthread_cancel (thr);
>> +
>> + void *status = xpthread_join (thr);
>> + TEST_VERIFY (status == PTHREAD_CANCELED);
>> + TEST_COMPARE (cl_called, 1);
>> +
>> + printf ("in-time cancel test of '%s' successful\n", tests[i].name);
> 
> The wait allows us to get to the syscall if possible.
> 
>> + }
>> +
>> + for (int i = 0; i < array_length (tests); i++)
>> + {
>> + xpthread_barrier_init (&barrier, NULL, 2);
>> + /* Reset the counter for the cleanup handler. */
>> + cl_called = 0;
>> +
>> + pthread_t thr = xpthread_create (0, tests[i].tf, NULL);
> 
> /* After this wait the cancellation handler is in place. */

Ack.

> 
>> + xpthread_barrier_wait (&barrier);
>> +
>> + xpthread_cancel (thr);
>> + xpthread_barrier_wait (&barrier);
> 
> OK. We are guarnateed the handler is in place. We will *not* see the syscall called
> because of the second barrier.
> 
>> +
>> + void *status = xpthread_join (thr);
>> + TEST_VERIFY (status == PTHREAD_CANCELED);
>> + TEST_COMPARE (cl_called, 1);
> 
> OK.
> 
>> +
>> + printf ("early cancel test of '%s' successful\n", tests[i].name);
> 
> OK.
> 
>> + }
>> +
>> + xpthread_barrier_destroy (&barrier);
>> +
>> + return 0;
>> +}
>> +
>> +#include <support/test-driver.c>
>> diff --git a/debug/tst-read-chk-cancel.c b/debug/tst-read-chk-cancel.c
>> deleted file mode 100644
>> index 7e06afb596..0000000000
>> --- a/debug/tst-read-chk-cancel.c
>> +++ /dev/null
>> @@ -1,50 +0,0 @@
>> -/* Test that __read_chk is a cancellation point (BZ #29274)
>> - Copyright (C) 2022 Free Software Foundation, Inc.
>> - This file is part of the GNU C Library.
>> -
>> - The GNU C Library is free software; you can redistribute it and/or
>> - modify it under the terms of the GNU Lesser General Public
>> - License as published by the Free Software Foundation; either
>> - version 2.1 of the License, or (at your option) any later version.
>> -
>> - The GNU C Library is distributed in the hope that it will be useful,
>> - but WITHOUT ANY WARRANTY; without even the implied warranty of
>> - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
>> - Lesser General Public License for more details.
>> -
>> - You should have received a copy of the GNU Lesser General Public
>> - License along with the GNU C Library; if not, see
>> - <https://www.gnu.org/licenses/>. */
>> -
>> -#include <stdint.h>
>> -#include <support/xunistd.h>
>> -#include <support/xthread.h>
>> -
>> -static int pipe_fds[2];
>> -static pthread_barrier_t barrier;
>> -
>> -static void *
>> -read_thread (void *n)
>> -{
>> - xpthread_barrier_wait (&barrier);
>> - char c;
>> - /* This call should be forwarded to __read_chk because the buffer size
>> - is known, but the read length is non-constant. */
>> - if (read (pipe_fds[0], &c, (uintptr_t) n) != 1)
>> - return (void *) -1L;
>> - return 0;
>> -}
>> -
>> -static int
>> -do_test (void)
>> -{
>> - xpthread_barrier_init (&barrier, 0, 2);
>> - xpipe (pipe_fds);
>> - pthread_t thr = xpthread_create (0, read_thread, (void *) 1L);
>> - xpthread_barrier_wait (&barrier);
>> - xpthread_cancel (thr);
>> - xpthread_join (thr);
>> - return 0;
>> -}
>> -
>> -#include <support/test-driver.c>
> 
> 
> -- 
> Cheers,
> Carlos.
  

Patch

diff --git a/debug/Makefile b/debug/Makefile
index 456b349c4d..99cdf3221b 100644
--- a/debug/Makefile
+++ b/debug/Makefile
@@ -110,7 +110,7 @@  CPPFLAGS-tst-longjmp_chk2.c += -D_FORTIFY_SOURCE=1
 CFLAGS-tst-longjmp_chk3.c += -fexceptions -fasynchronous-unwind-tables
 CPPFLAGS-tst-longjmp_chk3.c += -D_FORTIFY_SOURCE=1
 CPPFLAGS-tst-realpath-chk.c += -D_FORTIFY_SOURCE=2
-CPPFLAGS-tst-read-chk-cancel.c += -D_FORTIFY_SOURCE=2
+CPPFLAGS-tst-chk-cancel.c += -D_FORTIFY_SOURCE=2
 
 # _FORTIFY_SOURCE tests.
 # Auto-generate tests for _FORTIFY_SOURCE for different levels, compilers and
@@ -206,7 +206,7 @@  tests += tst-ssp-1
 endif
 
 ifeq ($(have-thread-library), yes)
-tests += tst-read-chk-cancel
+tests += tst-chk-cancel
 endif
 
 ifeq (,$(CXX))
@@ -248,4 +248,4 @@  $(objpfx)xtrace: xtrace.sh
 	    -e 's|@REPORT_BUGS_TO@|$(REPORT_BUGS_TO)|' $^ > $@.new \
 	&& rm -f $@ && mv $@.new $@ && chmod +x $@
 
-$(objpfx)tst-read-chk-cancel: $(shared-thread-library)
+$(objpfx)tst-chk-cancel: $(shared-thread-library)
diff --git a/debug/tst-chk-cancel.c b/debug/tst-chk-cancel.c
new file mode 100644
index 0000000000..53530db489
--- /dev/null
+++ b/debug/tst-chk-cancel.c
@@ -0,0 +1,263 @@ 
+/* Test for required cancellation points in fortified functions (BZ #29274)
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <array_length.h>
+#include <errno.h>
+#include <poll.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <support/check.h>
+#include <support/xthread.h>
+#include <support/xunistd.h>
+#include <sys/socket.h>
+
+/* Cleanup handling test.  */
+static int cl_called;
+
+static void
+cl (void *arg)
+{
+  ++cl_called;
+}
+
+static int fds[2];
+static pthread_barrier_t barrier;
+
+static void *
+tf_read (void *n)
+{
+  xpthread_barrier_wait (&barrier);
+
+  if (n != (void *) 1L)
+    xpthread_barrier_wait (&barrier);
+
+  pthread_cleanup_push (cl, NULL);
+  /* This call should be forwarded to __read_chk because the buffer size
+     is known, but the read length is non-constant.  */
+  char c;
+  if (read (fds[0], &c, (uintptr_t) n) != 1)
+    return (void *) -1L;
+
+  pthread_cleanup_pop (0);
+  return 0;
+}
+
+static void *
+tf_pread (void *n)
+{
+  xpthread_barrier_wait (&barrier);
+
+  if (n != (void *) 1L)
+    xpthread_barrier_wait (&barrier);
+
+  pthread_cleanup_push (cl, NULL);
+  /* This call should be forwarded to __pread_chk because the buffer size
+     is known, but the read length is non-constant.  */
+  char c;
+  if (pread (fds[0], &c, (uintptr_t) n, 0) != 1)
+    return (void *) -1L;
+
+  pthread_cleanup_pop (0);
+  return 0;
+}
+
+static void *
+tf_pread64 (void *n)
+{
+  xpthread_barrier_wait (&barrier);
+
+  if (n != (void *) 1L)
+    xpthread_barrier_wait (&barrier);
+
+  pthread_cleanup_push (cl, NULL);
+  /* This call should be forwarded to __pread64_chk because the buffer size
+     is known, but the read length is non-constant.  */
+  char c;
+  if (pread64 (fds[0], &c, (uintptr_t) n, 0) != 1)
+    return (void *) -1L;
+
+  pthread_cleanup_pop (0);
+  return 0;
+}
+
+static void *
+tf_poll (void *n)
+{
+  xpthread_barrier_wait (&barrier);
+
+  if (n != (void *) 1L)
+    xpthread_barrier_wait (&barrier);
+
+  pthread_cleanup_push (cl, NULL);
+  /* This call should be forwarded to __poll_chk because the pollfd size
+     is known, but the number of entries is non-constant.  */
+  struct pollfd pfd = { fds[0], POLLIN, 0 };
+  if (poll (&pfd, (uintptr_t) n, -1) != 1)
+    return (void *) -1L;
+
+  pthread_cleanup_pop (0);
+  return 0;
+}
+
+static void *
+tf_ppoll (void *n)
+{
+  xpthread_barrier_wait (&barrier);
+
+  if (n != (void *) 1L)
+    xpthread_barrier_wait (&barrier);
+
+  pthread_cleanup_push (cl, NULL);
+  /* This call should be forwarded to __ppoll_chk because the pollfd size
+     is known, but the number of entries is non-constant.  */
+  struct pollfd pfd = { fds[0], POLLIN, 0 };
+  if (ppoll (&pfd, (uintptr_t) n, 0, 0) != 1)
+    return (void *) -1L;
+
+  pthread_cleanup_pop (0);
+  return 0;
+}
+
+static void *
+tf_recv (void *n)
+{
+  xpthread_barrier_wait (&barrier);
+
+  if (n != (void *) 1L)
+    xpthread_barrier_wait (&barrier);
+
+  pthread_cleanup_push (cl, NULL);
+  /* This call should be forwarded to __ppoll_chk because the pollfd size
+     is known, but the number of entries is non-constant.  */
+  char c;
+  if (recv (fds[0], &c, (uintptr_t) n, 0) != 1)
+    return (void *) -1L;
+
+  pthread_cleanup_pop (0);
+  return 0;
+}
+
+static void *
+tf_recvfrom (void *n)
+{
+  xpthread_barrier_wait (&barrier);
+
+  if (n != (void *) 1L)
+    xpthread_barrier_wait (&barrier);
+
+  pthread_cleanup_push (cl, NULL);
+  /* This call should be forwarded to __ppoll_chk because the pollfd size
+     is known, but the number of entries is non-constant.  */
+  char c;
+  if (recvfrom (fds[0], &c, (uintptr_t) n, 0, NULL, NULL) != 1)
+    return (void *) -1L;
+
+  pthread_cleanup_pop (0);
+  return 0;
+}
+
+static struct cancel_tests
+{
+  const char *name;
+  void *(*tf) (void *);
+  bool only_early;
+#define ADD_TEST(name, early) { #name, tf_##name, early }
+} tests[] =
+{
+  ADD_TEST (poll,     false),
+  ADD_TEST (ppoll,    false),
+  ADD_TEST (pread,    true),
+  ADD_TEST (pread64,  true),
+  ADD_TEST (read,     false),
+  ADD_TEST (recv,     false),
+  ADD_TEST (recvfrom, false),
+};
+
+/* Set the send buffer of socket S to 1 byte so any send operation
+   done with WRITE_BUFFER_SIZE bytes will force syscall blocking.  */
+static void
+set_socket_buffer (int s)
+{
+  int val = 1;
+  socklen_t len = sizeof (val);
+
+  TEST_VERIFY_EXIT (setsockopt (s, SOL_SOCKET, SO_SNDBUF, &val,
+                    sizeof (val)) == 0);
+  TEST_VERIFY_EXIT (getsockopt (s, SOL_SOCKET, SO_SNDBUF, &val, &len) == 0);
+  printf ("%s: got size %d\n", __func__, val);
+}
+
+static int
+do_test (void)
+{
+  xpthread_barrier_init (&barrier, 0, 2);
+
+  if (socketpair (AF_UNIX, SOCK_STREAM, 0, fds) != 0)
+    FAIL_EXIT1 ("socketpair: %m");
+  set_socket_buffer (fds[1]);
+
+  for (int i = 0; i < array_length (tests); i++)
+    {
+      if (tests[i].only_early)
+	continue;
+
+      xpthread_barrier_init (&barrier, NULL, 2);
+      /* Reset the counter for the cleanup handler.  */
+      cl_called = 0;
+
+      pthread_t thr = xpthread_create (0, tests[i].tf, (void *) 1L);
+      xpthread_barrier_wait (&barrier);
+
+      struct timespec ts = { .tv_sec = 0, .tv_nsec = 100000000 };
+      TEMP_FAILURE_RETRY (clock_nanosleep (CLOCK_REALTIME, 0, &ts, &ts));
+
+      xpthread_cancel (thr);
+
+      void *status = xpthread_join (thr);
+      TEST_VERIFY (status == PTHREAD_CANCELED);
+      TEST_COMPARE (cl_called, 1);
+
+      printf ("in-time cancel test of '%s' successful\n", tests[i].name);
+    }
+
+  for (int i = 0; i < array_length (tests); i++)
+    {
+      xpthread_barrier_init (&barrier, NULL, 2);
+      /* Reset the counter for the cleanup handler.  */
+      cl_called = 0;
+
+      pthread_t thr = xpthread_create (0, tests[i].tf, NULL);
+      xpthread_barrier_wait (&barrier);
+
+      xpthread_cancel (thr);
+      xpthread_barrier_wait (&barrier);
+
+      void *status = xpthread_join (thr);
+      TEST_VERIFY (status == PTHREAD_CANCELED);
+      TEST_COMPARE (cl_called, 1);
+
+      printf ("early cancel test of '%s' successful\n", tests[i].name);
+    }
+
+  xpthread_barrier_destroy (&barrier);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/debug/tst-read-chk-cancel.c b/debug/tst-read-chk-cancel.c
deleted file mode 100644
index 7e06afb596..0000000000
--- a/debug/tst-read-chk-cancel.c
+++ /dev/null
@@ -1,50 +0,0 @@ 
-/* Test that __read_chk is a cancellation point (BZ #29274)
-   Copyright (C) 2022 Free Software Foundation, Inc.
-   This file is part of the GNU C Library.
-
-   The GNU C Library is free software; you can redistribute it and/or
-   modify it under the terms of the GNU Lesser General Public
-   License as published by the Free Software Foundation; either
-   version 2.1 of the License, or (at your option) any later version.
-
-   The GNU C Library is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-   Lesser General Public License for more details.
-
-   You should have received a copy of the GNU Lesser General Public
-   License along with the GNU C Library; if not, see
-   <https://www.gnu.org/licenses/>.  */
-
-#include <stdint.h>
-#include <support/xunistd.h>
-#include <support/xthread.h>
-
-static int pipe_fds[2];
-static pthread_barrier_t barrier;
-
-static void *
-read_thread (void *n)
-{
-  xpthread_barrier_wait (&barrier);
-  char c;
-  /* This call should be forwarded to __read_chk because the buffer size
-     is known, but the read length is non-constant.  */
-  if (read (pipe_fds[0], &c, (uintptr_t) n) != 1)
-    return (void *) -1L;
-  return 0;
-}
-
-static int
-do_test (void)
-{
-  xpthread_barrier_init (&barrier, 0, 2);
-  xpipe (pipe_fds);
-  pthread_t thr = xpthread_create (0, read_thread, (void *) 1L);
-  xpthread_barrier_wait (&barrier);
-  xpthread_cancel (thr);
-  xpthread_join (thr);
-  return 0;
-}
-
-#include <support/test-driver.c>