diff mbox series

[RESEND] syscalls/prctl06.c: New test for prctl() with PR_{SET, GET}_NO_NEW_PRIVS

Message ID 1562366936-26456-1-git-send-email-xuyang2018.jy@cn.fujitsu.com
State Superseded
Headers show
Series [RESEND] syscalls/prctl06.c: New test for prctl() with PR_{SET, GET}_NO_NEW_PRIVS | expand

Commit Message

Yang Xu July 5, 2019, 10:48 p.m. UTC
Signed-off-by: Yang Xu <xuyang2018.jy@cn.fujitsu.com>
---
 include/lapi/prctl.h                          |   5 +
 runtest/syscalls                              |   1 +
 testcases/kernel/syscalls/prctl/.gitignore    |   1 +
 testcases/kernel/syscalls/prctl/prctl06.c     | 173 ++++++++++++++++++
 .../kernel/syscalls/prctl/prctl06_execve.c    |  65 +++++++
 5 files changed, 245 insertions(+)
 create mode 100644 testcases/kernel/syscalls/prctl/prctl06.c
 create mode 100644 testcases/kernel/syscalls/prctl/prctl06_execve.c

Comments

Cyril Hrubis July 10, 2019, 10:52 a.m. UTC | #1
Hi!
>  pread01 pread01
>  pread01_64 pread01_64
> diff --git a/testcases/kernel/syscalls/prctl/.gitignore b/testcases/kernel/syscalls/prctl/.gitignore
> index 9ecaf9854..f52f6f665 100644
> --- a/testcases/kernel/syscalls/prctl/.gitignore
> +++ b/testcases/kernel/syscalls/prctl/.gitignore
> @@ -3,3 +3,4 @@
>  /prctl03
>  /prctl04
>  /prctl05
> +/prctl06

Missing prctl06_execve

> diff --git a/testcases/kernel/syscalls/prctl/prctl06.c b/testcases/kernel/syscalls/prctl/prctl06.c
> new file mode 100644
> index 000000000..9dd82a241
> --- /dev/null
> +++ b/testcases/kernel/syscalls/prctl/prctl06.c
> @@ -0,0 +1,173 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (c) 2019 FUJITSU LIMITED. All rights reserved.
> + * Author: Yang Xu <xuyang2018.jy@cn.fujitsu.com>
> + *
> + * Test PR_GET_NO_NEW_PRIVS and PR_SET_NO_NEW_PRIVS of prctl(2).
> + *
> + * 1)Return the value of the no_new_privs bit for the calling thread.
> + *  A value of 0 indicates the regular execve(2) behavior.  A value of
> + *  1 indicates execve(2) will operate in the privilege-restricting mode.
> + * 2)With no_new_privs set to 1, diables privilege granting operations
> + *  at execve-time. For example, a process will not be able to execute a
> + *  setuid binary to change their uid or gid if this bit is set. The same
> + *  is true for file capabilities.
> + * 3)The setting of this bit is inherited by children created by fork(2).
> + *  We also check NoNewPrivs field in /proc/[pid]/status if it supports.
> + */
> +
> +#include <errno.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <sys/prctl.h>
> +#include <pwd.h>
> +#include <sys/types.h>
> +#include <unistd.h>
> +#include <sys/capability.h>
> +#include <lapi/prctl.h>
> +#include "tst_test.h"
> +
> +#define IPC_ENV_VAR        "LTP_IPC_PATH"
> +#define MNTPOINT           "mntpoint"
> +#define TESTBIN            "prctl06_execve"
> +#define TEST_REL_BIN_DIR   MNTPOINT"/"
> +#define SUID_MODE          (S_ISUID|S_ISGID|S_IXUSR|S_IXGRP|S_IXOTH)
> +
> +static int flag = 1;
> +static char CapEff[20];
> +
> +static void check_proc_field(int val, char *name)
> +{
> +	char path[50];
> +	pid_t pid;
> +	int field = 0;

Also it would be a bit cleaner if we do:

	if (flag)
		return;

here and called the function unconditionaly down below.

> +	pid = getpid();
> +	sprintf(path, "/proc/%d/status", pid);
                       ^
		       /proc/self/status ?

> +	TEST(FILE_LINES_SCANF(path, "NoNewPrivs:%d", &field));
> +	if (TST_RET == 1) {
> +		tst_res(TCONF,
> +			"/proc/[pid]/status doesn't support NoNewPrivs field");
> +		flag = 0;
> +		return;
> +	}
> +	if (val == field)
> +		tst_res(TPASS, "%s %s NoNewPrivs field expected %d got %d",
> +			name, path, val, field);
> +	else
> +		tst_res(TFAIL, "%s %s NoNewPrivs field expected %d got %d",
> +			name, path, val, field);
> +
> +	SAFE_FILE_LINES_SCANF(path, "CapEff:%s", CapEff);
> +}
> +
> +static void check_no_new_privs(int val, char *name)
> +{
> +	TEST(prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0));
> +	if (TST_RET == val)
> +		tst_res(TPASS,
> +			"%s prctl(PR_GET_NO_NEW_PRIVS) expected %d got %d",
> +			name, val, val);
> +	else
> +		tst_res(TFAIL,
> +			"%s prctl(PR_GET_NO_NEW_PRIVS) expected %d got %ld",
> +			name, val, TST_RET);
> +	if (flag)
> +		check_proc_field(val, name);
> +}
> +
> +static void do_prctl(void)
> +{
> +	char ipc_env_var[1024];
> +	char *const argv[] = {"prctl06_execve", "parent process", CapEff, NULL};
> +	char *const childargv[] = {"prctl06_execve", "child process", CapEff, NULL};
> +	char *const envp[] = {"LTP_TEST_ENV_VAR=test", ipc_env_var, NULL };
                                ^
				This is not really needed here. We use
				that only in the execve tests...
> +	int childpid;
> +	struct passwd *pw;
> +	uid_t nobody_uid;
> +	gid_t nobody_gid;
> +
> +	pw = SAFE_GETPWNAM("nobody");
> +	nobody_uid = pw->pw_uid;
> +	nobody_gid = pw->pw_gid;
          ^
	  This can be done once in test setup

> +	check_no_new_privs(0, "parent");
> +	tst_res(TINFO,
> +		"parent process CapEff %s when no new privs was 0", CapEff);
> +
> +	TEST(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
> +	if (TST_RET == -1) {
> +		tst_res(TFAIL | TTERRNO, "prctl(PR_SET_NO_NEW_PRIVS) failed");
> +		return;
> +	}
> +	tst_res(TPASS, "prctl(PR_SET_NO_NEW_PRIVS) succeeded");
> +
> +	SAFE_CHMOD("prctl06_execve", SUID_MODE);
         ^
	 This can be done in setup() as well.

> +	SAFE_SETGID(nobody_gid);
> +	SAFE_SETUID(nobody_uid);
> +
> +	sprintf(ipc_env_var, IPC_ENV_VAR "=%s", getenv(IPC_ENV_VAR));
> +
> +	childpid = SAFE_FORK();
> +	if (childpid == 0) {
> +		check_no_new_privs(1, "child");
> +		execve("prctl06_execve", childargv, envp);
> +		tst_brk(TFAIL | TTERRNO,
> +			"child process failed to execute prctl_execve");
> +
> +	} else {
> +		tst_reap_children();
> +		check_no_new_privs(1, "parent");
> +		tst_res(TINFO,
> +			"parent process CapEff %s when no new privs was 1", CapEff);
> +		execve("prctl06_execve", argv, envp);
> +		tst_brk(TFAIL | TTERRNO,
> +			"parent process failed to execute prctl_execve");
> +	}
> +}
> +
> +static void verify_prctl(void)
> +{
> +	int pid;
> +
> +	pid = SAFE_FORK();
> +	if (pid == 0) {
> +		do_prctl();
> +		exit(0);
> +	}
> +	tst_reap_children();

No need to reap children here if you do exit(0) the library will reap
them for you.

> +}
> +
> +static void setup(void)
> +{
> +	TEST(prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0));
> +	if (TST_RET == 0) {
> +		tst_res(TINFO, "kernel supports PR_GET/SET_NO_NEW_PRIVS");
> +		SAFE_CP(TESTBIN, TEST_REL_BIN_DIR);
> +		return;
> +	}
> +
> +	if (TST_ERR == EINVAL)
> +		tst_brk(TCONF,
> +			"kernel doesn't support PR_GET/SET_NO_NEW_PRIVS");
> +
> +	tst_brk(TBROK | TTERRNO,
> +		"current environment doesn't permit PR_GET/SET_NO_NEW_PRIVS");
> +}
> +
> +static const char *const resfile[] = {
> +	TESTBIN,
> +	NULL,
> +};
> +
> +static struct tst_test test = {
> +	.resource_files = resfile,
> +	.setup = setup,
> +	.test_all = verify_prctl,
> +	.forks_child = 1,
> +	.needs_root = 1,
> +	.mount_device = 1,
> +	.mntpoint = MNTPOINT,
> +	.child_needs_reinit = 1,
> +};
> diff --git a/testcases/kernel/syscalls/prctl/prctl06_execve.c b/testcases/kernel/syscalls/prctl/prctl06_execve.c
> new file mode 100644
> index 000000000..6b256afae
> --- /dev/null
> +++ b/testcases/kernel/syscalls/prctl/prctl06_execve.c
> @@ -0,0 +1,65 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (c) 2019 FUJITSU LIMITED. All rights reserved.
> + * Author: Yang Xu <xuyang2018.jy@cn.fujitsu.com>
> + *
> + * dummy program which is used by prctl06 testcase
> + */
> +#define TST_NO_DEFAULT_MAIN
> +#include <stdlib.h>
> +#include <sys/types.h>
> +#include <unistd.h>
> +#include <errno.h>
> +#include <pwd.h>
> +#include <stdio.h>
> +#include <string.h>
> +#include "tst_test.h"
> +
> +int main(int argc, char **argv)
> +{
> +	struct passwd *pw;
> +	uid_t unknown_uid;
> +	gid_t unknown_gid;
> +	char path[50];
> +	char CapEff[20];
> +	pid_t pid;
> +
> +	tst_reinit();
> +	if (argc != 3)
> +		tst_brk(TFAIL, "argc is %d, expected 3", argc);
> +
> +	pid = getpid();
> +	sprintf(path, "/proc/%d/status", pid);
                        ^
			/proc/self/status

> +	SAFE_FILE_LINES_SCANF(path, "CapEff:%s", CapEff);
> +
> +	if (strncmp(CapEff, argv[2], sizeof(CapEff)))
> +		tst_res(TFAIL,
> +			"%s gains root privileges, current CapEff %s, expect %s",
> +			argv[1], CapEff, argv[2]);
> +	else
> +		tst_res(TPASS,
> +			"%s doesn't gain root privileges, CapEff %s",
> +			argv[1], CapEff);
> +
> +	pw = SAFE_GETPWNAM("nobody");
> +	unknown_uid = pw->pw_uid + 1;
> +	unknown_gid = pw->pw_gid + 1;
> +
> +	TEST(setgid(unknown_gid));
> +	if (TST_RET == -1)
> +		tst_res(TPASS,
> +			"%s setgid(%d) isn't permmit", argv[1], unknown_gid);
> +	else
> +		tst_res(TFAIL, "%s setgid(%d) succeed expectedly",
> +			argv[1], unknown_gid);
> +
> +	TEST(setuid(unknown_uid));
> +	if (TST_RET == -1)
> +		tst_res(TPASS,
> +			"%s setuid(%d) isn't permmit", argv[1], unknown_uid);
> +	else
> +		tst_res(TFAIL, " %s setuid(%d) succeed unexpectedly",
> +			argv[1], unknown_gid);

We are executing setuid binary that was created by root here so
shouldn't we just check that getuid() and getgid() returns 0?

I guess that we can also chown the file to uid=0 and gid=0 once it has
been copied to be 100% sure that the ids are correct in the test setup.

> +	return 0;
> +}

Otherwise the test looks very good.


Also I guess that we need another test that would set the prctl value
and check that it cannot be unset. If you are going to do that please do
that in a separate file, this one is complex enough...
Yang Xu July 11, 2019, 7:57 a.m. UTC | #2
on 2019/07/10 18:52, Cyril Hrubis wrote:

> Hi!
>>   pread01 pread01
>>   pread01_64 pread01_64
>> diff --git a/testcases/kernel/syscalls/prctl/.gitignore b/testcases/kernel/syscalls/prctl/.gitignore
>> index 9ecaf9854..f52f6f665 100644
>> --- a/testcases/kernel/syscalls/prctl/.gitignore
>> +++ b/testcases/kernel/syscalls/prctl/.gitignore
>> @@ -3,3 +3,4 @@
>>   /prctl03
>>   /prctl04
>>   /prctl05
>> +/prctl06
> Missing prctl06_execve
OK.

>> diff --git a/testcases/kernel/syscalls/prctl/prctl06.c b/testcases/kernel/syscalls/prctl/prctl06.c
>> new file mode 100644
>> index 000000000..9dd82a241
>> --- /dev/null
>> +++ b/testcases/kernel/syscalls/prctl/prctl06.c
>> @@ -0,0 +1,173 @@
>> +// SPDX-License-Identifier: GPL-2.0-or-later
>> +/*
>> + * Copyright (c) 2019 FUJITSU LIMITED. All rights reserved.
>> + * Author: Yang Xu<xuyang2018.jy@cn.fujitsu.com>
>> + *
>> + * Test PR_GET_NO_NEW_PRIVS and PR_SET_NO_NEW_PRIVS of prctl(2).
>> + *
>> + * 1)Return the value of the no_new_privs bit for the calling thread.
>> + *  A value of 0 indicates the regular execve(2) behavior.  A value of
>> + *  1 indicates execve(2) will operate in the privilege-restricting mode.
>> + * 2)With no_new_privs set to 1, diables privilege granting operations
>> + *  at execve-time. For example, a process will not be able to execute a
>> + *  setuid binary to change their uid or gid if this bit is set. The same
>> + *  is true for file capabilities.
>> + * 3)The setting of this bit is inherited by children created by fork(2).
>> + *  We also check NoNewPrivs field in /proc/[pid]/status if it supports.
>> + */
>> +
>> +#include<errno.h>
>> +#include<stdio.h>
>> +#include<stdlib.h>
>> +#include<sys/prctl.h>
>> +#include<pwd.h>
>> +#include<sys/types.h>
>> +#include<unistd.h>
>> +#include<sys/capability.h>
>> +#include<lapi/prctl.h>
>> +#include "tst_test.h"
>> +
>> +#define IPC_ENV_VAR        "LTP_IPC_PATH"
>> +#define MNTPOINT           "mntpoint"
>> +#define TESTBIN            "prctl06_execve"
>> +#define TEST_REL_BIN_DIR   MNTPOINT"/"
>> +#define SUID_MODE          (S_ISUID|S_ISGID|S_IXUSR|S_IXGRP|S_IXOTH)
>> +
>> +static int flag = 1;
>> +static char CapEff[20];
>> +
>> +static void check_proc_field(int val, char *name)
>> +{
>> +	char path[50];
>> +	pid_t pid;
>> +	int field = 0;
> Also it would be a bit cleaner if we do:
>
> 	if (flag)
> 		return;
>
> here and called the function unconditionaly down below.
>
Call check_proc_field()  when flag is 1 in check_no_new_privs(). So we have avoideded this situation.

>> +	pid = getpid();
>> +	sprintf(path, "/proc/%d/status", pid);
>                         ^
> 		       /proc/self/status ?
>
Yes.

>> +	TEST(FILE_LINES_SCANF(path, "NoNewPrivs:%d",&field));
>> +	if (TST_RET == 1) {
>> +		tst_res(TCONF,
>> +			"/proc/[pid]/status doesn't support NoNewPrivs field");
>> +		flag = 0;
>> +		return;
>> +	}
>> +	if (val == field)
>> +		tst_res(TPASS, "%s %s NoNewPrivs field expected %d got %d",
>> +			name, path, val, field);
>> +	else
>> +		tst_res(TFAIL, "%s %s NoNewPrivs field expected %d got %d",
>> +			name, path, val, field);
>> +
>> +	SAFE_FILE_LINES_SCANF(path, "CapEff:%s", CapEff);
>> +}
>> +
>> +static void check_no_new_privs(int val, char *name)
>> +{
>> +	TEST(prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0));
>> +	if (TST_RET == val)
>> +		tst_res(TPASS,
>> +			"%s prctl(PR_GET_NO_NEW_PRIVS) expected %d got %d",
>> +			name, val, val);
>> +	else
>> +		tst_res(TFAIL,
>> +			"%s prctl(PR_GET_NO_NEW_PRIVS) expected %d got %ld",
>> +			name, val, TST_RET);
>> +	if (flag)
>> +		check_proc_field(val, name);
>> +}
>> +
>> +static void do_prctl(void)
>> +{
>> +	char ipc_env_var[1024];
>> +	char *const argv[] = {"prctl06_execve", "parent process", CapEff, NULL};
>> +	char *const childargv[] = {"prctl06_execve", "child process", CapEff, NULL};
>> +	char *const envp[] = {"LTP_TEST_ENV_VAR=test", ipc_env_var, NULL };
>                                  ^
> 				This is not really needed here. We use
> 				that only in the execve tests...
OK. I got it.
>> +	int childpid;
>> +	struct passwd *pw;
>> +	uid_t nobody_uid;
>> +	gid_t nobody_gid;
>> +
>> +	pw = SAFE_GETPWNAM("nobody");
>> +	nobody_uid = pw->pw_uid;
>> +	nobody_gid = pw->pw_gid;
>            ^
> 	  This can be done once in test setup
>
>> +	check_no_new_privs(0, "parent");
>> +	tst_res(TINFO,
>> +		"parent process CapEff %s when no new privs was 0", CapEff);
>> +
>> +	TEST(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
>> +	if (TST_RET == -1) {
>> +		tst_res(TFAIL | TTERRNO, "prctl(PR_SET_NO_NEW_PRIVS) failed");
>> +		return;
>> +	}
>> +	tst_res(TPASS, "prctl(PR_SET_NO_NEW_PRIVS) succeeded");
>> +
>> +	SAFE_CHMOD("prctl06_execve", SUID_MODE);
>           ^
> 	 This can be done in setup() as well.
>
OK. I will move them into setup() .

>> +	SAFE_SETGID(nobody_gid);
>> +	SAFE_SETUID(nobody_uid);
>> +
>> +	sprintf(ipc_env_var, IPC_ENV_VAR "=%s", getenv(IPC_ENV_VAR));
>> +
>> +	childpid = SAFE_FORK();
>> +	if (childpid == 0) {
>> +		check_no_new_privs(1, "child");
>> +		execve("prctl06_execve", childargv, envp);
>> +		tst_brk(TFAIL | TTERRNO,
>> +			"child process failed to execute prctl_execve");
>> +
>> +	} else {
>> +		tst_reap_children();
>> +		check_no_new_privs(1, "parent");
>> +		tst_res(TINFO,
>> +			"parent process CapEff %s when no new privs was 1", CapEff);
>> +		execve("prctl06_execve", argv, envp);
>> +		tst_brk(TFAIL | TTERRNO,
>> +			"parent process failed to execute prctl_execve");
>> +	}
>> +}
>> +
>> +static void verify_prctl(void)
>> +{
>> +	int pid;
>> +
>> +	pid = SAFE_FORK();
>> +	if (pid == 0) {
>> +		do_prctl();
>> +		exit(0);
>> +	}
>> +	tst_reap_children();
> No need to reap children here if you do exit(0) the library will reap
> them for you.
Yes.

>> +}
>> +
>> +static void setup(void)
>> +{
>> +	TEST(prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0));
>> +	if (TST_RET == 0) {
>> +		tst_res(TINFO, "kernel supports PR_GET/SET_NO_NEW_PRIVS");
>> +		SAFE_CP(TESTBIN, TEST_REL_BIN_DIR);
>> +		return;
>> +	}
>> +
>> +	if (TST_ERR == EINVAL)
>> +		tst_brk(TCONF,
>> +			"kernel doesn't support PR_GET/SET_NO_NEW_PRIVS");
>> +
>> +	tst_brk(TBROK | TTERRNO,
>> +		"current environment doesn't permit PR_GET/SET_NO_NEW_PRIVS");
>> +}
>> +
>> +static const char *const resfile[] = {
>> +	TESTBIN,
>> +	NULL,
>> +};
>> +
>> +static struct tst_test test = {
>> +	.resource_files = resfile,
>> +	.setup = setup,
>> +	.test_all = verify_prctl,
>> +	.forks_child = 1,
>> +	.needs_root = 1,
>> +	.mount_device = 1,
>> +	.mntpoint = MNTPOINT,
>> +	.child_needs_reinit = 1,
>> +};
>> diff --git a/testcases/kernel/syscalls/prctl/prctl06_execve.c b/testcases/kernel/syscalls/prctl/prctl06_execve.c
>> new file mode 100644
>> index 000000000..6b256afae
>> --- /dev/null
>> +++ b/testcases/kernel/syscalls/prctl/prctl06_execve.c
>> @@ -0,0 +1,65 @@
>> +// SPDX-License-Identifier: GPL-2.0-or-later
>> +/*
>> + * Copyright (c) 2019 FUJITSU LIMITED. All rights reserved.
>> + * Author: Yang Xu<xuyang2018.jy@cn.fujitsu.com>
>> + *
>> + * dummy program which is used by prctl06 testcase
>> + */
>> +#define TST_NO_DEFAULT_MAIN
>> +#include<stdlib.h>
>> +#include<sys/types.h>
>> +#include<unistd.h>
>> +#include<errno.h>
>> +#include<pwd.h>
>> +#include<stdio.h>
>> +#include<string.h>
>> +#include "tst_test.h"
>> +
>> +int main(int argc, char **argv)
>> +{
>> +	struct passwd *pw;
>> +	uid_t unknown_uid;
>> +	gid_t unknown_gid;
>> +	char path[50];
>> +	char CapEff[20];
>> +	pid_t pid;
>> +
>> +	tst_reinit();
>> +	if (argc != 3)
>> +		tst_brk(TFAIL, "argc is %d, expected 3", argc);
>> +
>> +	pid = getpid();
>> +	sprintf(path, "/proc/%d/status", pid);
>                          ^
> 			/proc/self/status
>
Yes.
>> +	SAFE_FILE_LINES_SCANF(path, "CapEff:%s", CapEff);
>> +
>> +	if (strncmp(CapEff, argv[2], sizeof(CapEff)))
>> +		tst_res(TFAIL,
>> +			"%s gains root privileges, current CapEff %s, expect %s",
>> +			argv[1], CapEff, argv[2]);
>> +	else
>> +		tst_res(TPASS,
>> +			"%s doesn't gain root privileges, CapEff %s",
>> +			argv[1], CapEff);
>> +
>> +	pw = SAFE_GETPWNAM("nobody");
>> +	unknown_uid = pw->pw_uid + 1;
>> +	unknown_gid = pw->pw_gid + 1;
>> +
>> +	TEST(setgid(unknown_gid));
>> +	if (TST_RET == -1)
>> +		tst_res(TPASS,
>> +			"%s setgid(%d) isn't permmit", argv[1], unknown_gid);
>> +	else
>> +		tst_res(TFAIL, "%s setgid(%d) succeed expectedly",
>> +			argv[1], unknown_gid);
>> +
>> +	TEST(setuid(unknown_uid));
>> +	if (TST_RET == -1)
>> +		tst_res(TPASS,
>> +			"%s setuid(%d) isn't permmit", argv[1], unknown_uid);
>> +	else
>> +		tst_res(TFAIL, " %s setuid(%d) succeed unexpectedly",
>> +			argv[1], unknown_gid);
> We are executing setuid binary that was created by root here so
> shouldn't we just check that getuid() and getgid() returns 0?
>
I try it.  whether we set or not set new privs, the getuid() or getgid() return nobody in
prctl06_execve.  Or, I misunderstand your advise?

Also, my design is that
1. copy the prctl06_execve binary to a $PWD
2. chmod it with S_ISUID, S_ISGID ,prctl06_exeve can be excuted under nobody
3. prctl06 SETGID and SETUID as nobody, set no_new_privs to 1 and excute prctl06_execve
4. the prct06_execve itself would check if it gained root priviledges(check proc/self/status capeff
and excute setuid and setgid action )


> I guess that we can also chown the file to uid=0 and gid=0 once it has
> been copied to be 100% sure that the ids are correct in the test setup.
Yes.
>> +	return 0;
>> +}
> Otherwise the test looks very good.
>
>
> Also I guess that we need another test that would set the prctl value
> and check that it cannot be unset. If you are going to do that please do
> that in a separate file, this one is complex enough...
OK. I will add this in a separate file.
Cyril Hrubis July 11, 2019, 11:34 a.m. UTC | #3
Hi!
> > We are executing setuid binary that was created by root here so
> > shouldn't we just check that getuid() and getgid() returns 0?
> >
> I try it.  whether we set or not set new privs, the getuid() or getgid() return nobody in
> prctl06_execve.  Or, I misunderstand your advise?

Looking closely into the manuals the setuid and setgid bits are supposed
to set the effective ids, so I guess that the geteuid() and getegid()
will return 0 when the process was executed without the prctl().
Yang Xu July 12, 2019, 4:34 a.m. UTC | #4
> Hi!
>>> We are executing setuid binary that was created by root here so
>>> shouldn't we just check that getuid() and getgid() returns 0?
>>>
>> I try it.  whether we set or not set new privs, the getuid() or getgid() return nobody in
>> prctl06_execve.  Or, I misunderstand your advise?
> Looking closely into the manuals the setuid and setgid bits are supposed
> to set the effective ids, so I guess that the geteuid() and getegid()
> will return 0 when the process was executed without the prctl().
>

Hi Cyril

Yes. I will remove capeff and setuid,setgid check in my v3 patch.  For the prctl value
and check that it cannot be unset, I think it is a error test as manpage said
"prctl() fails with EINVAL when options is PR_SET_NO_NEW_PRIVS&  arg2 is not equal to 1 or arg3, arg4, or arg5 is nonzero."

I will add it into prctl02.c.  Also, there are many error conditions for prctl. So I think when my prctl07.c are merged into

ltp, I will increase prctl02.c together.

Thanks
Yang Xu
diff mbox series

Patch

diff --git a/include/lapi/prctl.h b/include/lapi/prctl.h
index ad0b12bce..54b3da20f 100644
--- a/include/lapi/prctl.h
+++ b/include/lapi/prctl.h
@@ -24,4 +24,9 @@ 
 # define PR_GET_CHILD_SUBREAPER	37
 #endif
 
+#ifndef PR_SET_NO_NEW_PRIVS
+# define PR_SET_NO_NEW_PRIVS 38
+# define PR_GET_NO_NEW_PRIVS 39
+#endif
+
 #endif /* LAPI_PRCTL_H__ */
diff --git a/runtest/syscalls b/runtest/syscalls
index 702d6a8c7..a9cad6748 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -860,6 +860,7 @@  prctl02 prctl02
 prctl03 prctl03
 prctl04 prctl04
 prctl05 prctl05
+prctl06 prctl06
 
 pread01 pread01
 pread01_64 pread01_64
diff --git a/testcases/kernel/syscalls/prctl/.gitignore b/testcases/kernel/syscalls/prctl/.gitignore
index 9ecaf9854..f52f6f665 100644
--- a/testcases/kernel/syscalls/prctl/.gitignore
+++ b/testcases/kernel/syscalls/prctl/.gitignore
@@ -3,3 +3,4 @@ 
 /prctl03
 /prctl04
 /prctl05
+/prctl06
diff --git a/testcases/kernel/syscalls/prctl/prctl06.c b/testcases/kernel/syscalls/prctl/prctl06.c
new file mode 100644
index 000000000..9dd82a241
--- /dev/null
+++ b/testcases/kernel/syscalls/prctl/prctl06.c
@@ -0,0 +1,173 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2019 FUJITSU LIMITED. All rights reserved.
+ * Author: Yang Xu <xuyang2018.jy@cn.fujitsu.com>
+ *
+ * Test PR_GET_NO_NEW_PRIVS and PR_SET_NO_NEW_PRIVS of prctl(2).
+ *
+ * 1)Return the value of the no_new_privs bit for the calling thread.
+ *  A value of 0 indicates the regular execve(2) behavior.  A value of
+ *  1 indicates execve(2) will operate in the privilege-restricting mode.
+ * 2)With no_new_privs set to 1, diables privilege granting operations
+ *  at execve-time. For example, a process will not be able to execute a
+ *  setuid binary to change their uid or gid if this bit is set. The same
+ *  is true for file capabilities.
+ * 3)The setting of this bit is inherited by children created by fork(2).
+ *  We also check NoNewPrivs field in /proc/[pid]/status if it supports.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/prctl.h>
+#include <pwd.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/capability.h>
+#include <lapi/prctl.h>
+#include "tst_test.h"
+
+#define IPC_ENV_VAR        "LTP_IPC_PATH"
+#define MNTPOINT           "mntpoint"
+#define TESTBIN            "prctl06_execve"
+#define TEST_REL_BIN_DIR   MNTPOINT"/"
+#define SUID_MODE          (S_ISUID|S_ISGID|S_IXUSR|S_IXGRP|S_IXOTH)
+
+static int flag = 1;
+static char CapEff[20];
+
+static void check_proc_field(int val, char *name)
+{
+	char path[50];
+	pid_t pid;
+	int field = 0;
+
+	pid = getpid();
+	sprintf(path, "/proc/%d/status", pid);
+
+	TEST(FILE_LINES_SCANF(path, "NoNewPrivs:%d", &field));
+	if (TST_RET == 1) {
+		tst_res(TCONF,
+			"/proc/[pid]/status doesn't support NoNewPrivs field");
+		flag = 0;
+		return;
+	}
+	if (val == field)
+		tst_res(TPASS, "%s %s NoNewPrivs field expected %d got %d",
+			name, path, val, field);
+	else
+		tst_res(TFAIL, "%s %s NoNewPrivs field expected %d got %d",
+			name, path, val, field);
+
+	SAFE_FILE_LINES_SCANF(path, "CapEff:%s", CapEff);
+}
+
+static void check_no_new_privs(int val, char *name)
+{
+	TEST(prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0));
+	if (TST_RET == val)
+		tst_res(TPASS,
+			"%s prctl(PR_GET_NO_NEW_PRIVS) expected %d got %d",
+			name, val, val);
+	else
+		tst_res(TFAIL,
+			"%s prctl(PR_GET_NO_NEW_PRIVS) expected %d got %ld",
+			name, val, TST_RET);
+	if (flag)
+		check_proc_field(val, name);
+}
+
+static void do_prctl(void)
+{
+	char ipc_env_var[1024];
+	char *const argv[] = {"prctl06_execve", "parent process", CapEff, NULL};
+	char *const childargv[] = {"prctl06_execve", "child process", CapEff, NULL};
+	char *const envp[] = {"LTP_TEST_ENV_VAR=test", ipc_env_var, NULL };
+	int childpid;
+	struct passwd *pw;
+	uid_t nobody_uid;
+	gid_t nobody_gid;
+
+	pw = SAFE_GETPWNAM("nobody");
+	nobody_uid = pw->pw_uid;
+	nobody_gid = pw->pw_gid;
+
+	check_no_new_privs(0, "parent");
+	tst_res(TINFO,
+		"parent process CapEff %s when no new privs was 0", CapEff);
+
+	TEST(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
+	if (TST_RET == -1) {
+		tst_res(TFAIL | TTERRNO, "prctl(PR_SET_NO_NEW_PRIVS) failed");
+		return;
+	}
+	tst_res(TPASS, "prctl(PR_SET_NO_NEW_PRIVS) succeeded");
+
+	SAFE_CHMOD("prctl06_execve", SUID_MODE);
+	SAFE_SETGID(nobody_gid);
+	SAFE_SETUID(nobody_uid);
+
+	sprintf(ipc_env_var, IPC_ENV_VAR "=%s", getenv(IPC_ENV_VAR));
+
+	childpid = SAFE_FORK();
+	if (childpid == 0) {
+		check_no_new_privs(1, "child");
+		execve("prctl06_execve", childargv, envp);
+		tst_brk(TFAIL | TTERRNO,
+			"child process failed to execute prctl_execve");
+
+	} else {
+		tst_reap_children();
+		check_no_new_privs(1, "parent");
+		tst_res(TINFO,
+			"parent process CapEff %s when no new privs was 1", CapEff);
+		execve("prctl06_execve", argv, envp);
+		tst_brk(TFAIL | TTERRNO,
+			"parent process failed to execute prctl_execve");
+	}
+}
+
+static void verify_prctl(void)
+{
+	int pid;
+
+	pid = SAFE_FORK();
+	if (pid == 0) {
+		do_prctl();
+		exit(0);
+	}
+	tst_reap_children();
+}
+
+static void setup(void)
+{
+	TEST(prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0));
+	if (TST_RET == 0) {
+		tst_res(TINFO, "kernel supports PR_GET/SET_NO_NEW_PRIVS");
+		SAFE_CP(TESTBIN, TEST_REL_BIN_DIR);
+		return;
+	}
+
+	if (TST_ERR == EINVAL)
+		tst_brk(TCONF,
+			"kernel doesn't support PR_GET/SET_NO_NEW_PRIVS");
+
+	tst_brk(TBROK | TTERRNO,
+		"current environment doesn't permit PR_GET/SET_NO_NEW_PRIVS");
+}
+
+static const char *const resfile[] = {
+	TESTBIN,
+	NULL,
+};
+
+static struct tst_test test = {
+	.resource_files = resfile,
+	.setup = setup,
+	.test_all = verify_prctl,
+	.forks_child = 1,
+	.needs_root = 1,
+	.mount_device = 1,
+	.mntpoint = MNTPOINT,
+	.child_needs_reinit = 1,
+};
diff --git a/testcases/kernel/syscalls/prctl/prctl06_execve.c b/testcases/kernel/syscalls/prctl/prctl06_execve.c
new file mode 100644
index 000000000..6b256afae
--- /dev/null
+++ b/testcases/kernel/syscalls/prctl/prctl06_execve.c
@@ -0,0 +1,65 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2019 FUJITSU LIMITED. All rights reserved.
+ * Author: Yang Xu <xuyang2018.jy@cn.fujitsu.com>
+ *
+ * dummy program which is used by prctl06 testcase
+ */
+#define TST_NO_DEFAULT_MAIN
+#include <stdlib.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <errno.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <string.h>
+#include "tst_test.h"
+
+int main(int argc, char **argv)
+{
+	struct passwd *pw;
+	uid_t unknown_uid;
+	gid_t unknown_gid;
+	char path[50];
+	char CapEff[20];
+	pid_t pid;
+
+	tst_reinit();
+	if (argc != 3)
+		tst_brk(TFAIL, "argc is %d, expected 3", argc);
+
+	pid = getpid();
+	sprintf(path, "/proc/%d/status", pid);
+	SAFE_FILE_LINES_SCANF(path, "CapEff:%s", CapEff);
+
+	if (strncmp(CapEff, argv[2], sizeof(CapEff)))
+		tst_res(TFAIL,
+			"%s gains root privileges, current CapEff %s, expect %s",
+			argv[1], CapEff, argv[2]);
+	else
+		tst_res(TPASS,
+			"%s doesn't gain root privileges, CapEff %s",
+			argv[1], CapEff);
+
+	pw = SAFE_GETPWNAM("nobody");
+	unknown_uid = pw->pw_uid + 1;
+	unknown_gid = pw->pw_gid + 1;
+
+	TEST(setgid(unknown_gid));
+	if (TST_RET == -1)
+		tst_res(TPASS,
+			"%s setgid(%d) isn't permmit", argv[1], unknown_gid);
+	else
+		tst_res(TFAIL, "%s setgid(%d) succeed expectedly",
+			argv[1], unknown_gid);
+
+	TEST(setuid(unknown_uid));
+	if (TST_RET == -1)
+		tst_res(TPASS,
+			"%s setuid(%d) isn't permmit", argv[1], unknown_uid);
+	else
+		tst_res(TFAIL, " %s setuid(%d) succeed unexpectedly",
+			argv[1], unknown_gid);
+
+	return 0;
+}