[LTP] [PATCH] ptrace/ptrace07: Add test for PTRACE_{G, S}ETSIGMASK

Cyril Hrubis chrubis@suse.cz
Fri Jun 30 14:03:30 CEST 2017


Hi!
> --- /dev/null
> +++ b/testcases/kernel/syscalls/ptrace/ptrace07.c
> @@ -0,0 +1,269 @@
> +/*
> + * Copyright (c) ARM Limited, 2017.
> + *
> + * Based on ptrace02.c:
> + * Copyright (c) Wipro Technologies Ltd, 2002.  All Rights Reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of version 2 of the GNU General Public License as
> + * published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it would be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
> + *
> + * You should have received a copy of the GNU General Public License along
> + * with this program; if not, write the Free Software Foundation, Inc.,
> + */
> +
> +/*
> + * Child masks SIGUSR1 then waits to receive this signal from its parent.
> + * Parent ptrace()s child to unmask SIGUSR1 then sends this signal.
> + */
> +
> +#include <sys/types.h>
> +
> +/*
> + * We need to know how big the kernel's sigset_t is:
> + */
> +#define sigset_t kernel_sigset_t
> +#define sigaltstack kernel_sigaltstack
> +#define sigaction kernel_sigaction
> +#define stack_t kernel_stack_t
> +#include <asm-generic/signal.h>
> +#undef sigset_t
> +#undef sigaltstack
> +#undef sigaction
> +#undef stack_t
> +
> +#include <errno.h>
> +#include <sched.h>
> +#include <signal.h>
> +#include <sys/time.h>
> +#include <sys/wait.h>
> +#include <unistd.h>
> +
> +#include <config.h>
> +#include "ptrace.h"
> +
> +#include "test.h"

First of all test.h is deprecated for quite some time, new testcases
should use the new test library aka "tst_test.h".

> +static int do_child(void);
> +static void setup(void);
> +static void cleanup(void);
> +
> +static void parent_handler(int signo, siginfo_t *si, void *ctx);
> +static void child_handler(int signo, siginfo_t *si, void *ctx);
> +
> +char *TCID = "ptrace07";
> +int TST_TOTAL = 1;
> +
> +#define PIPE_READ  0
> +#define PIPE_WRITE 1
> +static int pipefd[2];
> +
> +int main(int ac, char **av)
> +{
> +	fd_set fds;
> +	sigset_t sigs;
> +	int rv, status;
> +	pid_t child_pid;
> +	struct timeval tv;
> +
> +	tst_parse_opts(ac, av, NULL, NULL);
> +
> +	setup();
> +
> +	if (pipe(pipefd) == -1) {
> +		tst_resm(TBROK, "pipe() failed");
> +		tst_exit();
> +	}
> +
> +	switch (child_pid = fork()) {
> +	case -1:
> +		/* fork() failed */
> +		tst_resm(TBROK, "fork() failed");
> +	case 0:		/* Child */
> +		close(pipefd[PIPE_READ]);
> +		do_child();
> +		abort();
> +	default:	/* Parent */
> +		close(pipefd[PIPE_WRITE]);
> +
> +		/* Wait for child to become ready */
> +		rv = read(pipefd[PIPE_READ], &rv, 1);
> +		if (rv != 1) {
> +			tst_resm(TBROK, "failed to read from pipe: %u", errno);
> +			break;
> +		}

We do have a futex() based synchronization primitives for this,
have a look at:

https://github.com/linux-test-project/ltp/wiki/Test-Writing-Guidelines#229-fork-and-parent-child-synchronization

> +		/* Attach to child */
> +		rv = ptrace(PTRACE_ATTACH, child_pid, NULL, NULL);
> +		if (rv) {
> +			tst_resm(TBROK, "ptrace() failed to attach: %u", errno);
> +			break;
> +		}
> +
> +		/* Wait for the child to stop */
> +		rv = waitpid(child_pid, &status, 0);
> +		if (rv == -1) {
> +			tst_resm(TBROK, "waitpid() failed: %u", errno);
> +			break;
> +		}

We also have SAFE_WAITPID() that includes error checking, see:
https://github.com/linux-test-project/ltp/wiki/Test-Writing-Guidelines#224-safe-macros

> +		/* Inspect child signals: SIGUSR1 should be masked */
> +		rv = ptrace(PTRACE_GETSIGMASK, child_pid,
> +			    sizeof(kernel_sigset_t), &sigs);
> +		if (rv) {
> +			tst_resm(TBROK, "ptrace() failed: %u", errno);
> +			break;
> +		}
> +		if (!sigismember(&sigs, SIGUSR1)) {
> +			tst_resm(TFAIL, "Child SIGUSR1 not masked");
> +			break;
> +		}
> +
> +		/* Unmask child signals */
> +		rv = sigemptyset(&sigs);
> +		if (rv) {
> +			tst_resm(TBROK, "sigemptyset() failed: %u", errno);
> +			break;
> +		}
> +		rv = ptrace(PTRACE_SETSIGMASK, child_pid,
> +			    sizeof(kernel_sigset_t), &sigs);
> +		if (rv) {
> +			tst_resm(TBROK, "ptrace() failed: %u", errno);
> +			break;
> +		}
> +
> +		/* Restart child and deliver signal */
> +		rv = ptrace(PTRACE_CONT, child_pid, NULL, SIGUSR1);
> +		if (rv) {
> +			tst_resm(TBROK, "ptrace() failed: %u", errno);
> +			break;
> +		}
> +
> +		/* Wait for the child to signal receipt, or timeout */
> +		FD_ZERO(&fds);
> +		FD_SET(pipefd[PIPE_READ], &fds);
> +		tv.tv_sec = 1;
> +		tv.tv_usec = 0;
> +
> +		do {
> +			errno = 0;
> +			rv = select(pipefd[PIPE_READ]+1, &fds, NULL, NULL, &tv);
> +		} while (errno == EINTR);
> +
> +		if (rv == -1) {
> +			tst_resm(TBROK, "select() failed: %u", errno);
> +		} else if (rv == 0) {
> +			/* Timeout */
> +			tst_resm(TFAIL, "timeout: Signals not delivered");
> +		} else {
> +			/* Write from child indicates signal received */
> +			tst_resm(TPASS,
> +				 "ptrace() successfully unmasked signal");
> +		}
> +	}
> +
> +	if (child_pid != -1)
> +		kill(child_pid, SIGKILL);
> +
> +	/* cleanup and exit */
> +	cleanup();
> +	tst_exit();
> +
> +}
> +
> +static int do_child(void)
> +{
> +	long rv;
> +	sigset_t sigs;
> +	struct sigaction act = {0};
> +
> +	act.sa_sigaction = &child_handler;
> +	act.sa_flags = SA_SIGINFO;
> +	sigemptyset(&act.sa_mask);
> +
> +	rv = sigaction(SIGUSR1, &act, NULL);
> +	if (rv) {
> +		tst_resm(TBROK, "sigaction() failed in child");
> +		tst_exit();
> +	}
> +
> +	/* Mask SIGUSR1 */
> +	rv = sigemptyset(&sigs);
> +	if (rv) {
> +		tst_resm(TBROK, "sigemptyset() failed");
> +		tst_exit();
> +	}
> +	rv = sigaddset(&sigs, SIGUSR1);
> +	if (rv) {
> +		tst_resm(TBROK, "sigaddset() failed");
> +		tst_exit();
> +	}
> +	rv = sigprocmask(SIG_BLOCK, &sigs, NULL);
> +	if (rv) {
> +		tst_resm(TBROK, "sigprocmask() failed");
> +		tst_exit();
> +	}
> +
> +	/* Ready */
> +	rv = write(pipefd[PIPE_WRITE], &rv, 1);
> +	if (rv == -1) {
> +		tst_resm(TBROK, "Failed to write to parent: %u", errno);
> +		tst_exit();
> +	}
> +
> +	/* Receive signals from parent */
> +	while (1)
> +		sched_yield();
> +}
> +
> +void setup(void)
> +{
> +	int rv;
> +	struct sigaction act = {0};
> +
> +	tst_sig(FORK, DEF_HANDLER, cleanup);
> +
> +	act.sa_sigaction = &parent_handler;
> +	act.sa_flags = SA_SIGINFO;
> +	sigemptyset(&act.sa_mask);
> +
> +	rv = sigaction(SIGCHLD, &act, NULL);
> +	if (rv) {
> +		tst_resm(TFAIL, "sigaction() failed");
> +		tst_exit();
> +	}
> +
> +	TEST_PAUSE;
> +}
> +
> +void cleanup(void)
> +{
> +
> +}
> +
> +static void parent_handler(int signo, __attribute__((unused)) siginfo_t *si,
> +			   __attribute__((unused)) void *ctx)
> +{
> +	if (signo != SIGCHLD) {
> +		tst_resm(TBROK, "parent_handler() received unexpected signal.");
> +		tst_exit();

You cannot do this in the signal handler. Basically you can use only
async-signal-safe functions (man 7 signal). Otherwise you may cause
dead-locks and any kind of undefined behavior.

> +	}
> +}
> +
> +static void child_handler(int signo, __attribute__((unused)) siginfo_t *si,
> +			  __attribute__((unused)) void *ctx)
> +{
> +	int rv = 1;
> +
> +	if (signo == SIGUSR1) {
> +		rv = write(pipefd[PIPE_WRITE], &rv, 1);
> +		if (rv == -1)
> +			tst_resm(TBROK, "Failed to write to parent: %u", errno);
> +	}

Here as well.

The most safe course of action would be suspending the child in pause()
here instead of doing the bussy loop with sched_yield(), then just doing
exit(0) while the parent sleeps in SAFE_WAIT().

> +
> +	exit(rv);
> +}
> -- 
> 2.11.0
> 
> 
> -- 
> Mailing list info: https://lists.linux.it/listinfo/ltp

-- 
Cyril Hrubis
chrubis@suse.cz


More information about the ltp mailing list