mirror of
				https://gitlab.gnome.org/GNOME/glib.git
				synced 2025-11-04 01:58:54 +01:00 
			
		
		
		
	gio/tests: Add test case for exit status when being ptraced
The test case will fail with the
    g_assert_false (g_subprocess_get_successful (proc));
assert failing. Without the fix, it'll hit sometimes, but rather
unreliably. When running `meson test --repeat 100`, it'll reproduce
anywhere between the first or much later, but mostly before the 20th
iteration on my system.
Helps: #3071
			
			
This commit is contained in:
		
				
					committed by
					
						
						Philip Withnall
					
				
			
			
				
	
			
			
			
						parent
						
							8d78fa7887
						
					
				
				
					commit
					cf55c31170
				
			@@ -5,6 +5,7 @@
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#ifdef G_OS_UNIX
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include <sys/ptrace.h>
 | 
			
		||||
#else
 | 
			
		||||
#include <io.h>
 | 
			
		||||
#endif
 | 
			
		||||
@@ -241,6 +242,66 @@ printenv_mode (int argc, char **argv)
 | 
			
		||||
  return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef G_OS_UNIX
 | 
			
		||||
static void
 | 
			
		||||
on_sleep_exited (GObject         *object,
 | 
			
		||||
                 GAsyncResult    *result,
 | 
			
		||||
                 gpointer         user_data)
 | 
			
		||||
{
 | 
			
		||||
  GSubprocess *subprocess = G_SUBPROCESS (object);
 | 
			
		||||
  gboolean *done = user_data;
 | 
			
		||||
  GError *local_error = NULL;
 | 
			
		||||
  gboolean ret;
 | 
			
		||||
 | 
			
		||||
  ret = g_subprocess_wait_finish (subprocess, result, &local_error);
 | 
			
		||||
  g_assert_no_error (local_error);
 | 
			
		||||
  g_assert_true (ret);
 | 
			
		||||
 | 
			
		||||
  *done = TRUE;
 | 
			
		||||
  g_main_context_wakeup (NULL);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int
 | 
			
		||||
sleep_and_kill (int argc, char **argv)
 | 
			
		||||
{
 | 
			
		||||
  GPtrArray *args = NULL;
 | 
			
		||||
  GSubprocessLauncher *launcher = NULL;
 | 
			
		||||
  GSubprocess *proc = NULL;
 | 
			
		||||
  GError *local_error = NULL;
 | 
			
		||||
  pid_t sleep_pid;
 | 
			
		||||
  gboolean done = FALSE;
 | 
			
		||||
 | 
			
		||||
  args = g_ptr_array_new_with_free_func (g_free);
 | 
			
		||||
 | 
			
		||||
  /* Run sleep "forever" in a shell; this will trigger PTRACE_EVENT_EXEC */
 | 
			
		||||
  g_ptr_array_add (args, g_strdup ("sh"));
 | 
			
		||||
  g_ptr_array_add (args, g_strdup ("-c"));
 | 
			
		||||
  g_ptr_array_add (args, g_strdup ("sleep infinity"));
 | 
			
		||||
  g_ptr_array_add (args, NULL);
 | 
			
		||||
  launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE);
 | 
			
		||||
  proc = g_subprocess_launcher_spawnv (launcher, (const gchar **) args->pdata, &local_error);
 | 
			
		||||
  g_assert_no_error (local_error);
 | 
			
		||||
  g_assert_nonnull (proc);
 | 
			
		||||
 | 
			
		||||
  sleep_pid = atoi (g_subprocess_get_identifier (proc));
 | 
			
		||||
 | 
			
		||||
  g_subprocess_wait_async (proc, NULL, on_sleep_exited, &done);
 | 
			
		||||
 | 
			
		||||
  kill (sleep_pid, SIGKILL);
 | 
			
		||||
 | 
			
		||||
  while (!done)
 | 
			
		||||
    g_main_context_iteration (NULL, TRUE);
 | 
			
		||||
 | 
			
		||||
  g_assert_false (g_subprocess_get_successful (proc));
 | 
			
		||||
 | 
			
		||||
  g_clear_pointer (&args, g_ptr_array_unref);
 | 
			
		||||
  g_clear_object (&launcher);
 | 
			
		||||
  g_clear_object (&proc);
 | 
			
		||||
 | 
			
		||||
  return EXIT_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
main (int argc, char **argv)
 | 
			
		||||
{
 | 
			
		||||
@@ -267,6 +328,8 @@ main (int argc, char **argv)
 | 
			
		||||
      return 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  g_log_writer_default_set_use_stderr (TRUE);
 | 
			
		||||
 | 
			
		||||
  mode = argv[1];
 | 
			
		||||
  if (strcmp (mode, "noop") == 0)
 | 
			
		||||
    return 0;
 | 
			
		||||
@@ -297,6 +360,10 @@ main (int argc, char **argv)
 | 
			
		||||
    return cwd_mode (argc, argv);
 | 
			
		||||
  else if (strcmp (mode, "printenv") == 0)
 | 
			
		||||
    return printenv_mode (argc, argv);
 | 
			
		||||
#ifdef G_OS_UNIX
 | 
			
		||||
  else if (strcmp (mode, "sleep-and-kill") == 0)
 | 
			
		||||
    return sleep_and_kill (argc, argv);
 | 
			
		||||
#endif
 | 
			
		||||
  else
 | 
			
		||||
    {
 | 
			
		||||
      g_printerr ("Unknown MODE %s\n", argv[1]);
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
#ifdef G_OS_UNIX
 | 
			
		||||
#include <sys/ptrace.h>
 | 
			
		||||
#include <sys/wait.h>
 | 
			
		||||
#include <glib-unix.h>
 | 
			
		||||
#include <gio/gunixinputstream.h>
 | 
			
		||||
@@ -1989,7 +1990,107 @@ test_fd_conflation_child_err_report_fd (void)
 | 
			
		||||
  do_test_fd_conflation (G_SUBPROCESS_FLAGS_NONE, empty_child_setup, TRUE);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
/* Handle ptrace events on @main_child, and assert that when it exits, it does
 | 
			
		||||
 * so with status %EXIT_SUCCESS, rather than signalling. Other than that, this
 | 
			
		||||
 * just calls %PTRACE_CONT for all trace events. */
 | 
			
		||||
static void
 | 
			
		||||
trace_children (pid_t main_child)
 | 
			
		||||
{
 | 
			
		||||
  int wstatus;
 | 
			
		||||
 | 
			
		||||
  g_assert_no_errno (waitpid (main_child, &wstatus, 0));
 | 
			
		||||
  g_assert_no_errno (ptrace (PTRACE_SETOPTIONS, main_child, NULL,
 | 
			
		||||
                             (PTRACE_O_TRACEFORK |
 | 
			
		||||
                              PTRACE_O_EXITKILL |
 | 
			
		||||
                              PTRACE_O_TRACEVFORK |
 | 
			
		||||
                              PTRACE_O_TRACECLONE |
 | 
			
		||||
                              PTRACE_O_TRACEEXEC)));
 | 
			
		||||
  g_assert_no_errno (ptrace (PTRACE_CONT, main_child, NULL, 0));
 | 
			
		||||
 | 
			
		||||
  while (TRUE)
 | 
			
		||||
    {
 | 
			
		||||
      pid_t pid;
 | 
			
		||||
      int wstatus;
 | 
			
		||||
      int stop_signum;
 | 
			
		||||
      int ptrace_event;
 | 
			
		||||
 | 
			
		||||
      pid = waitpid (-1, &wstatus, 0);
 | 
			
		||||
      if (pid == -1 && errno == ECHILD)
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      g_assert_cmpint (errno, ==, 0);
 | 
			
		||||
      g_assert_cmpint (pid, >=, 0);
 | 
			
		||||
 | 
			
		||||
      if (WIFSTOPPED (wstatus))
 | 
			
		||||
        stop_signum = WSTOPSIG (wstatus);
 | 
			
		||||
      else
 | 
			
		||||
        stop_signum = 0;
 | 
			
		||||
 | 
			
		||||
      switch (stop_signum)
 | 
			
		||||
        {
 | 
			
		||||
        case SIGTRAP:
 | 
			
		||||
          ptrace_event = (wstatus >> 16) & 0xffff;
 | 
			
		||||
          switch (ptrace_event)
 | 
			
		||||
            {
 | 
			
		||||
            case 0:
 | 
			
		||||
              g_assert_no_errno (ptrace (PTRACE_CONT, pid, NULL, stop_signum));
 | 
			
		||||
              break;
 | 
			
		||||
            default:
 | 
			
		||||
              g_assert_no_errno (ptrace (PTRACE_CONT, pid, NULL, 0));
 | 
			
		||||
              break;
 | 
			
		||||
            }
 | 
			
		||||
          break;
 | 
			
		||||
        case SIGSTOP:
 | 
			
		||||
          g_assert_no_errno (ptrace (PTRACE_CONT, pid, NULL, 0));
 | 
			
		||||
          break;
 | 
			
		||||
        default:
 | 
			
		||||
          if (!WIFEXITED (wstatus) && !WIFSIGNALED (wstatus))
 | 
			
		||||
            g_assert_no_errno (ptrace (PTRACE_CONT, pid, NULL, stop_signum));
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
      if (pid == main_child)
 | 
			
		||||
        {
 | 
			
		||||
          g_assert_false (WIFSIGNALED (wstatus));
 | 
			
		||||
          if (WIFEXITED (wstatus))
 | 
			
		||||
            {
 | 
			
		||||
              g_assert_cmpint (WEXITSTATUS (wstatus), ==, EXIT_SUCCESS);
 | 
			
		||||
              break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
test_exit_status_trapped (void)
 | 
			
		||||
{
 | 
			
		||||
  GPtrArray *args = NULL;
 | 
			
		||||
  pid_t test_child;
 | 
			
		||||
 | 
			
		||||
  g_test_summary ("Test that exit status is reported correctly for ptrace()d child processes");
 | 
			
		||||
  g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/merge_requests/3433");
 | 
			
		||||
 | 
			
		||||
  /* Call fork() directly here, rather than using #GSubprocess, so that we can
 | 
			
		||||
   * safely call waitpid() on it ourselves without interfering with the internals
 | 
			
		||||
   * of #GSubprocess.
 | 
			
		||||
   * See https://gitlab.gnome.org/GNOME/glib/-/merge_requests/3433#note_1749055 */
 | 
			
		||||
  args = get_test_subprocess_args ("sleep-and-kill", NULL);
 | 
			
		||||
  test_child = fork ();
 | 
			
		||||
  if (test_child == 0)
 | 
			
		||||
    {
 | 
			
		||||
      /* Between fork() and exec() we can only call async-signal-safe functions. */
 | 
			
		||||
      if (ptrace (PTRACE_TRACEME, 0, NULL, NULL) < 0)
 | 
			
		||||
        abort ();
 | 
			
		||||
 | 
			
		||||
      g_assert_no_errno (execvp (args->pdata[0], (char * const *) args->pdata));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  trace_children (test_child);
 | 
			
		||||
 | 
			
		||||
  g_clear_pointer (&args, g_ptr_array_unref);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif  /* G_OS_UNIX */
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
test_launcher_environment (void)
 | 
			
		||||
@@ -2133,6 +2234,7 @@ main (int argc, char **argv)
 | 
			
		||||
  g_test_add_func ("/gsubprocess/fd-conflation/empty-child-setup", test_fd_conflation_empty_child_setup);
 | 
			
		||||
  g_test_add_func ("/gsubprocess/fd-conflation/inherit-fds", test_fd_conflation_inherit_fds);
 | 
			
		||||
  g_test_add_func ("/gsubprocess/fd-conflation/child-err-report-fd", test_fd_conflation_child_err_report_fd);
 | 
			
		||||
  g_test_add_func ("/gsubprocess/exit-status/trapped", test_exit_status_trapped);
 | 
			
		||||
#endif
 | 
			
		||||
  g_test_add_func ("/gsubprocess/launcher-environment", test_launcher_environment);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user