diff mbox series

[2/2] selftests/exec: add a test for execveat()'s comm

Message ID 20241030203732.248767-2-tycho@tycho.pizza
State New
Headers show
Series [1/2] exec: fix up /proc/pid/comm in the execveat(AT_EMPTY_PATH) case | expand

Commit Message

Tycho Andersen Oct. 30, 2024, 8:37 p.m. UTC
From: Tycho Andersen <tandersen@netflix.com>

In the previous patch we've defined a couple behaviors:

1. execveat(fd, AT_EMPTY_PATH, {"foo"}, ...) should render argv[0] as
   /proc/pid/comm
2. execveat(fd, AT_EMPTY_PATH, {NULL}, ...) should keep the old behavior of
   rendering the fd as /proc/pid/comm

and just to be sure keeps working with symlinks, which was a concern in
[1], I've added a test for that as well.

The test itself is a bit ugly, because the existing check_execveat_fail()
helpers use a hardcoded envp and argv, and we want to "pass" things via the
environment to test various argument values, but it seemed cleaner than
passing one in everywhere in all the existing tests.

Output looks like:

    ok 51 Check success of execveat(6, 'home/tycho/packages/...yyyyyyyyyyyyyyyyyyyy', 0)...
    # Check execveat(AT_EMPTY_PATH)'s comm is sentinel
    ok 52 Check success of execveat(9, '', 4096)...
    # Check execveat(AT_EMPTY_PATH)'s comm is sentinel
    ok 53 Check success of execveat(11, '', 4096)...
    # Check execveat(AT_EMPTY_PATH)'s comm is 9
    [   25.579272] process 'execveat' launched '/dev/fd/9' with NULL argv: empty string added
    ok 54 Check success of execveat(9, '', 4096)...

[1]: https://lore.kernel.org/all/20240925.152228-private.conflict.frozen.trios-TdUGhuI5Sb4v@cyphar.com/
Signed-off-by: Tycho Andersen <tandersen@netflix.com>
---
v4: fix up commit message, use ksft_perror() vs perror(), Shuah
---
 tools/testing/selftests/exec/execveat.c | 77 ++++++++++++++++++++++++-
 1 file changed, 74 insertions(+), 3 deletions(-)

Comments

Mark Brown Nov. 27, 2024, 2:25 p.m. UTC | #1
On Wed, Oct 30, 2024 at 02:37:32PM -0600, Tycho Andersen wrote:
> From: Tycho Andersen <tandersen@netflix.com>
> 
> In the previous patch we've defined a couple behaviors:
> 
> 1. execveat(fd, AT_EMPTY_PATH, {"foo"}, ...) should render argv[0] as
>    /proc/pid/comm
> 2. execveat(fd, AT_EMPTY_PATH, {NULL}, ...) should keep the old behavior of
>    rendering the fd as /proc/pid/comm
> 
> and just to be sure keeps working with symlinks, which was a concern in
> [1], I've added a test for that as well.
> 
> The test itself is a bit ugly, because the existing check_execveat_fail()
> helpers use a hardcoded envp and argv, and we want to "pass" things via the
> environment to test various argument values, but it seemed cleaner than
> passing one in everywhere in all the existing tests.

This test doesn't pass in my CI, running on an i.MX8MP Verdin board.
This is an arm64 system and I'm running the tests on NFS.

> Output looks like:

>     ok 51 Check success of execveat(6, 'home/tycho/packages/...yyyyyyyyyyyyyyyyyyyy', 0)...
>     # Check execveat(AT_EMPTY_PATH)'s comm is sentinel
>     ok 52 Check success of execveat(9, '', 4096)...
>     # Check execveat(AT_EMPTY_PATH)'s comm is sentinel
>     ok 53 Check success of execveat(11, '', 4096)...
>     # Check execveat(AT_EMPTY_PATH)'s comm is 9
>     [   25.579272] process 'execveat' launched '/dev/fd/9' with NULL argv: empty string added
>     ok 54 Check success of execveat(9, '', 4096)...

The output when things fail is:

# # Check execveat(AT_EMPTY_PATH)'s comm is sentinel
# # bad comm, got: 11 expected: sentinel# child 8257 exited with 1 neither 0 nor 0
# not ok 52 Check success of execveat(11, '', 4096)... 
# # Check execveat(AT_EMPTY_PATH)'s comm is sentinel
# # bad comm, got: 13 expected: sentinel# child 8258 exited with 1 neither 0 nor 0
# not ok 53 Check success of execveat(13, '', 4096)... 

Full log from a failing job at:

   https://lava.sirena.org.uk/scheduler/job/993508

I didn't do any investigation beyond this.
Mark Brown Nov. 27, 2024, 3:03 p.m. UTC | #2
On Wed, Nov 27, 2024 at 08:00:12AM -0700, Tycho Andersen wrote:
> On Wed, Nov 27, 2024 at 02:25:29PM +0000, Mark Brown wrote:

> > This test doesn't pass in my CI, running on an i.MX8MP Verdin board.
> > This is an arm64 system and I'm running the tests on NFS.

> Strange... but this series has been rejected by Linus anyway, so
> probably not worth investigating further.

Ah, OK - it's still in -next and causing the overall suite to fail.
diff mbox series

Patch

diff --git a/tools/testing/selftests/exec/execveat.c b/tools/testing/selftests/exec/execveat.c
index 071e03532cba..3a05f8cbd815 100644
--- a/tools/testing/selftests/exec/execveat.c
+++ b/tools/testing/selftests/exec/execveat.c
@@ -23,9 +23,11 @@ 
 
 #include "../kselftest.h"
 
-#define TESTS_EXPECTED 51
+#define TESTS_EXPECTED 54
 #define TEST_NAME_LEN (PATH_MAX * 4)
 
+#define CHECK_COMM "CHECK_COMM"
+
 static char longpath[2 * PATH_MAX] = "";
 static char *envp[] = { "IN_TEST=yes", NULL, NULL };
 static char *argv[] = { "execveat", "99", NULL };
@@ -237,12 +239,36 @@  static int check_execveat_pathmax(int root_dfd, const char *src, int is_script)
 	return fail;
 }
 
+static int check_execveat_comm(int fd, char *argv0, char *expected)
+{
+	char buf[128], *old_env, *old_argv0;
+	int ret;
+
+	snprintf(buf, sizeof(buf), CHECK_COMM "=%s", expected);
+
+	old_env = envp[1];
+	envp[1] = buf;
+
+	old_argv0 = argv[0];
+	argv[0] = argv0;
+
+	ksft_print_msg("Check execveat(AT_EMPTY_PATH)'s comm is %s\n",
+		       expected);
+	ret = check_execveat_invoked_rc(fd, "", AT_EMPTY_PATH, 0, 0);
+
+	envp[1] = old_env;
+	argv[0] = old_argv0;
+
+	return ret;
+}
+
 static int run_tests(void)
 {
 	int fail = 0;
 	char *fullname = realpath("execveat", NULL);
 	char *fullname_script = realpath("script", NULL);
 	char *fullname_symlink = concat(fullname, ".symlink");
+	char fd_buf[10];
 	int subdir_dfd = open_or_die("subdir", O_DIRECTORY|O_RDONLY);
 	int subdir_dfd_ephemeral = open_or_die("subdir.ephemeral",
 					       O_DIRECTORY|O_RDONLY);
@@ -389,6 +415,15 @@  static int run_tests(void)
 
 	fail += check_execveat_pathmax(root_dfd, "execveat", 0);
 	fail += check_execveat_pathmax(root_dfd, "script", 1);
+
+	/* /proc/pid/comm gives argv[0] by default */
+	fail += check_execveat_comm(fd, "sentinel", "sentinel");
+	/* /proc/pid/comm gives argv[0] when invoked via link */
+	fail += check_execveat_comm(fd_symlink, "sentinel", "sentinel");
+	/* /proc/pid/comm gives fdno if NULL is passed */
+	snprintf(fd_buf, sizeof(fd_buf), "%d", fd);
+	fail += check_execveat_comm(fd, NULL, fd_buf);
+
 	return fail;
 }
 
@@ -415,9 +450,13 @@  int main(int argc, char **argv)
 	int ii;
 	int rc;
 	const char *verbose = getenv("VERBOSE");
+	const char *check_comm = getenv(CHECK_COMM);
 
-	if (argc >= 2) {
-		/* If we are invoked with an argument, don't run tests. */
+	if (argc >= 2 || check_comm) {
+		/*
+		 * If we are invoked with an argument, or no arguments but a
+		 * command to check, don't run tests.
+		 */
 		const char *in_test = getenv("IN_TEST");
 
 		if (verbose) {
@@ -426,6 +465,38 @@  int main(int argc, char **argv)
 				ksft_print_msg("\t[%d]='%s\n'", ii, argv[ii]);
 		}
 
+		/* If the tests wanted us to check the command, do so. */
+		if (check_comm) {
+			/* TASK_COMM_LEN == 16 */
+			char buf[32];
+			int fd, ret;
+
+			fd = open("/proc/self/comm", O_RDONLY);
+			if (fd < 0) {
+				ksft_perror("open() comm failed");
+				exit(1);
+			}
+
+			ret = read(fd, buf, sizeof(buf));
+			if (ret < 0) {
+				ksft_perror("read() comm failed");
+				close(fd);
+				exit(1);
+			}
+			close(fd);
+
+			// trim off the \n
+			buf[ret-1] = 0;
+
+			if (strcmp(buf, check_comm)) {
+				ksft_print_msg("bad comm, got: %s expected: %s",
+					       buf, check_comm);
+				exit(1);
+			}
+
+			exit(0);
+		}
+
 		/* Check expected environment transferred. */
 		if (!in_test || strcmp(in_test, "yes") != 0) {
 			ksft_print_msg("no IN_TEST=yes in env\n");