diff mbox series

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

Message ID 1557404498-3879-1-git-send-email-xuyang2018.jy@cn.fujitsu.com
State Changes Requested
Headers show
Series syscalls/prctl06.c: New test for prctl() with PR_{SET, GET}_NO_NEW_PRIVS | expand

Commit Message

Yang Xu May 9, 2019, 12:21 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/Makefile      |   2 +
 testcases/kernel/syscalls/prctl/prctl06.c     | 117 ++++++++++++++++++
 .../kernel/syscalls/prctl/prctl06_execve.c    |  46 +++++++
 6 files changed, 172 insertions(+)
 create mode 100644 testcases/kernel/syscalls/prctl/prctl06.c
 create mode 100644 testcases/kernel/syscalls/prctl/prctl06_execve.c

Comments

Yang Xu May 22, 2019, 10:18 a.m. UTC | #1
Hi
Ping.:-)
> 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/Makefile      |   2 +
>  testcases/kernel/syscalls/prctl/prctl06.c     | 117 ++++++++++++++++++
>  .../kernel/syscalls/prctl/prctl06_execve.c    |  46 +++++++
>  6 files changed, 172 insertions(+)
>  create mode 100644 testcases/kernel/syscalls/prctl/prctl06.c
>  create mode 100644 testcases/kernel/syscalls/prctl/prctl06_execve.c
>
> diff --git a/include/lapi/prctl.h b/include/lapi/prctl.h
> index 91da9c2d6..663ce19e9 100644
> --- a/include/lapi/prctl.h
> +++ b/include/lapi/prctl.h
> @@ -24,4 +24,9 @@
>  # define PR_SET_SECCOMP  22
>  #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 950615bef..a43bf5e4d 100644
> --- a/runtest/syscalls
> +++ b/runtest/syscalls
> @@ -865,6 +865,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/Makefile b/testcases/kernel/syscalls/prctl/Makefile
> index bd617d806..99a9d42e7 100644
> --- a/testcases/kernel/syscalls/prctl/Makefile
> +++ b/testcases/kernel/syscalls/prctl/Makefile
> @@ -20,4 +20,6 @@ top_srcdir		?= ../../../..
>  
>  include $(top_srcdir)/include/mk/testcases.mk
>  
> +LDLIBS			+= $(CAP_LIBS)
> +
>  include $(top_srcdir)/include/mk/generic_leaf_target.mk
> diff --git a/testcases/kernel/syscalls/prctl/prctl06.c b/testcases/kernel/syscalls/prctl/prctl06.c
> new file mode 100644
> index 000000000..ba5c2bcfb
> --- /dev/null
> +++ b/testcases/kernel/syscalls/prctl/prctl06.c
> @@ -0,0 +1,117 @@
> +// 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 (as the function result) 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, execve(2) promises not to grant privileges
> + *   to do anything that could not have been done without the execve(2)
> + *   call(for example, rendering the set-user-ID and set-group-ID mode bits)
> + * 3)The setting of this bit is inherited by children created by fork(2) and
> + *   clone(2), and preserved across execve(2).
> + */
> +
> +#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"
> +
> +static void check_no_new_privs(int val)
> +{
> +	TEST(prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0));
> +	if (TST_RET == val)
> +		tst_res(TPASS,
> +			"prctl(PR_GET_NO_NEW_PRIVS) got %d "
> +			"when no_new_privs was %d", val, val);
> +	else
> +		tst_res(TFAIL | TTERRNO,
> +			"prctl(PR_GET_NO_NEW_PRIVS) expected %d got %ld",
> +			val, TST_RET);
> +	return;
> +}
> +
> +static void do_prctl(void)
> +{
> +	char path[4096];
> +	char ipc_env_var[1024];
> +	char *const argv[] = {"prctl06_execve", "parent process", NULL};
> +	char *const childargv[] = {"prctl06_execve", "child process", NULL};
> +	char *const envp[] = {"LTP_TEST_ENV_VAR=test", ipc_env_var, NULL };
> +	cap_t caps = cap_init();
> +	cap_value_t capList = CAP_SETGID;
> +	unsigned int num_caps = 1;
> +	int childpid;
> +
> +	cap_set_flag(caps, CAP_EFFECTIVE, num_caps, &capList, CAP_SET);
> +	cap_set_flag(caps, CAP_INHERITABLE, num_caps, &capList, CAP_SET);
> +	cap_set_flag(caps, CAP_PERMITTED, num_caps, &capList, CAP_SET);
> +
> +	if (cap_set_proc(caps))
> +		tst_brk(TFAIL | TERRNO,
> +			"cap_set_flag(CAP_SETGID) failed");
> +	tst_res(TINFO, "cap_set_flag(CAP_SETGID) succeeded");
> +
> +	check_no_new_privs(0);
> +
> +	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");
> +
> +	check_no_new_privs(1);
> +
> +	if (tst_get_path("prctl06_execve", path, sizeof(path)))
> +		tst_brk(TCONF, "Couldn't find prctl_execve in $PATH");
> +
> +	sprintf(ipc_env_var, IPC_ENV_VAR "=%s", getenv(IPC_ENV_VAR));
> +	childpid = SAFE_FORK();
> +	if (childpid == 0) {
> +		check_no_new_privs(1);
> +		execve(path, childargv, envp);
> +		tst_brk(TFAIL | TERRNO,
> +			"child process failed to execute prctl_execve");
> +
> +	} else {
> +		tst_reap_children();
> +		execve(path, argv, envp);
> +		tst_brk(TFAIL | TERRNO,
> +			"parent process failed to execute prctl_execve");
> +	}
> +	cap_free(caps);
> +	return;
> +}
> +
> +static void verify_prctl(void)
> +{
> +	int pid;
> +
> +	pid = SAFE_FORK();
> +	if (pid == 0) {
> +		do_prctl();
> +		exit(0);
> +	}
> +	tst_reap_children();
> +	return;
> +}
> +
> +static struct tst_test test = {
> +	.test_all = verify_prctl,
> +	.forks_child = 1,
> +	.needs_root = 1,
> +	.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..84c28551c
> --- /dev/null
> +++ b/testcases/kernel/syscalls/prctl/prctl06_execve.c
> @@ -0,0 +1,46 @@
> +// 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 "tst_test.h"
> +
> +int main(int argc, char **argv)
> +{
> +	struct passwd *pw;
> +	uid_t nobody_uid;
> +	gid_t nobody_gid;
> +
> +	tst_reinit();
> +	if (argc != 2)
> +		tst_brk(TFAIL, "argc is %d, expected 2", argc);
> +
> +	pw = SAFE_GETPWNAM("nobody");
> +	nobody_uid = pw->pw_uid;
> +	nobody_gid = pw->pw_gid;
> +	/*positive check*/
> +	TEST(setgid(nobody_gid));
> +	if (TST_RET == -1)
> +		tst_res(TFAIL | TTERRNO,
> +			"%s setgid(%d) isn't permmit", argv[1], nobody_gid);
> +	else
> +		tst_res(TPASS, "%s setgid(%d) succeed expectedly",
> +			argv[1], nobody_gid);
> +	/*negative check*/
> +	TEST(setuid(nobody_uid));
> +	if (TST_RET == -1)
> +		tst_res(TPASS | TTERRNO,
> +			"%s setuid(%d) isn't permmit", argv[1], nobody_uid);
> +	else
> +		tst_res(TFAIL, " %s setuid(%d) succeed unexpectedly",
> +			argv[1], nobody_gid);
> +	return 0;
> +}
Cyril Hrubis May 23, 2019, 11:52 a.m. UTC | #2
Hi!
> +#define IPC_ENV_VAR "LTP_IPC_PATH"
> +
> +static void check_no_new_privs(int val)
> +{
> +	TEST(prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0));
> +	if (TST_RET == val)
> +		tst_res(TPASS,
> +			"prctl(PR_GET_NO_NEW_PRIVS) got %d "
> +			"when no_new_privs was %d", val, val);
> +	else
> +		tst_res(TFAIL | TTERRNO,
> +			"prctl(PR_GET_NO_NEW_PRIVS) expected %d got %ld",
> +			val, TST_RET);
> +	return;

This return is useless.

> +}
> +
> +static void do_prctl(void)
> +{
> +	char path[4096];
> +	char ipc_env_var[1024];
> +	char *const argv[] = {"prctl06_execve", "parent process", NULL};
> +	char *const childargv[] = {"prctl06_execve", "child process", NULL};
> +	char *const envp[] = {"LTP_TEST_ENV_VAR=test", ipc_env_var, NULL };
> +	cap_t caps = cap_init();
> +	cap_value_t capList = CAP_SETGID;
> +	unsigned int num_caps = 1;
> +	int childpid;
> +
> +	cap_set_flag(caps, CAP_EFFECTIVE, num_caps, &capList, CAP_SET);
> +	cap_set_flag(caps, CAP_INHERITABLE, num_caps, &capList, CAP_SET);
> +	cap_set_flag(caps, CAP_PERMITTED, num_caps, &capList, CAP_SET);
> +
> +	if (cap_set_proc(caps))
> +		tst_brk(TFAIL | TERRNO,
> +			"cap_set_flag(CAP_SETGID) failed");

You cannot use tst_brk with TFAIL, the best you can do here is to use
tst_ret(TFAIL ... ) then return; as you do below.

> +	tst_res(TINFO, "cap_set_flag(CAP_SETGID) succeeded");
> +
> +	check_no_new_privs(0);
> +
> +	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");
> +
> +	check_no_new_privs(1);
> +
> +	if (tst_get_path("prctl06_execve", path, sizeof(path)))
> +		tst_brk(TCONF, "Couldn't find prctl_execve in $PATH");

If the path to the binary is in $PATH you don't have to execute the
binary by an absolute path, passing it's name to execve() should
suffice.

> +	sprintf(ipc_env_var, IPC_ENV_VAR "=%s", getenv(IPC_ENV_VAR));
> +	childpid = SAFE_FORK();
> +	if (childpid == 0) {
> +		check_no_new_privs(1);

		Maybe we can pass a "child" string here and "parent"
		string in the cases above so that we can print if the
		check was done in child/parent in the tst_res() inside
		of this function.

> +		execve(path, childargv, envp);
> +		tst_brk(TFAIL | TERRNO,
> +			"child process failed to execute prctl_execve");
> +
> +	} else {
> +		tst_reap_children();
> +		execve(path, argv, envp);
> +		tst_brk(TFAIL | TERRNO,
> +			"parent process failed to execute prctl_execve");
> +	}
> +	cap_free(caps);
> +	return;
> +}
> +
> +static void verify_prctl(void)
> +{
> +	int pid;
> +
> +	pid = SAFE_FORK();
> +	if (pid == 0) {
> +		do_prctl();
> +		exit(0);
> +	}
> +	tst_reap_children();
> +	return;
> +}
> +
> +static struct tst_test test = {
> +	.test_all = verify_prctl,
> +	.forks_child = 1,
> +	.needs_root = 1,
> +	.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..84c28551c
> --- /dev/null
> +++ b/testcases/kernel/syscalls/prctl/prctl06_execve.c
> @@ -0,0 +1,46 @@
> +// 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 "tst_test.h"
> +
> +int main(int argc, char **argv)
> +{
> +	struct passwd *pw;
> +	uid_t nobody_uid;
> +	gid_t nobody_gid;
> +
> +	tst_reinit();
> +	if (argc != 2)
> +		tst_brk(TFAIL, "argc is %d, expected 2", argc);
> +
> +	pw = SAFE_GETPWNAM("nobody");
> +	nobody_uid = pw->pw_uid;
> +	nobody_gid = pw->pw_gid;
> +	/*positive check*/
> +	TEST(setgid(nobody_gid));
> +	if (TST_RET == -1)
> +		tst_res(TFAIL | TTERRNO,
> +			"%s setgid(%d) isn't permmit", argv[1], nobody_gid);
> +	else
> +		tst_res(TPASS, "%s setgid(%d) succeed expectedly",
> +			argv[1], nobody_gid);
> +	/*negative check*/
> +	TEST(setuid(nobody_uid));
> +	if (TST_RET == -1)
> +		tst_res(TPASS | TTERRNO,
> +			"%s setuid(%d) isn't permmit", argv[1], nobody_uid);
> +	else
> +		tst_res(TFAIL, " %s setuid(%d) succeed unexpectedly",
> +			argv[1], nobody_gid);
> +	return 0;
> +}

I do not think that this is actually testing the prctl in question. Here
we actually check that capabilities were inherited over fork() + exec().

As far as I understand the PR_SET_NO_NEW_PRIVS it has been designed
expecially to avoid misuse of capabilities associated with a particular
file.

So the check would have to do:

1. copy the prctl06_execve binary to a $PWD
2. chmod it with S_ISUID, S_ISGID
3. the prct06_execve would be executed under user/group nobody
4. the prct06_execve itself would check if it gained root priviledges

And we can do the same for capablities as well.

I guess that we would have to format a device and mount it with support
for capabilities for the test, since as far as I can tell you cannot
usually do neither of set-usr-id or add capabilites to files in /tmp/.
Yang Xu June 14, 2019, 10:32 a.m. UTC | #3
Hi Cyril
   Sorry for the late reply.

> Hi!
>> +#define IPC_ENV_VAR "LTP_IPC_PATH"
>> +
>> +static void check_no_new_privs(int val)
>> +{
>> +	TEST(prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0));
>> +	if (TST_RET == val)
>> +		tst_res(TPASS,
>> +			"prctl(PR_GET_NO_NEW_PRIVS) got %d "
>> +			"when no_new_privs was %d", val, val);
>> +	else
>> +		tst_res(TFAIL | TTERRNO,
>> +			"prctl(PR_GET_NO_NEW_PRIVS) expected %d got %ld",
>> +			val, TST_RET);
>> +	return;
> This return is useless.
OK. I will remove it and add check for /proc/[pid]/status for NoNewPrivs.

>> +}
>> +
>> +static void do_prctl(void)
>> +{
>> +	char path[4096];
>> +	char ipc_env_var[1024];
>> +	char *const argv[] = {"prctl06_execve", "parent process", NULL};
>> +	char *const childargv[] = {"prctl06_execve", "child process", NULL};
>> +	char *const envp[] = {"LTP_TEST_ENV_VAR=test", ipc_env_var, NULL };
>> +	cap_t caps = cap_init();
>> +	cap_value_t capList = CAP_SETGID;
>> +	unsigned int num_caps = 1;
>> +	int childpid;
>> +
>> +	cap_set_flag(caps, CAP_EFFECTIVE, num_caps,&capList, CAP_SET);
>> +	cap_set_flag(caps, CAP_INHERITABLE, num_caps,&capList, CAP_SET);
>> +	cap_set_flag(caps, CAP_PERMITTED, num_caps,&capList, CAP_SET);
>> +
>> +	if (cap_set_proc(caps))
>> +		tst_brk(TFAIL | TERRNO,
>> +			"cap_set_flag(CAP_SETGID) failed");
> You cannot use tst_brk with TFAIL, the best you can do here is to use
> tst_ret(TFAIL ... ) then return; as you do below.
>
I got it.

>> +	tst_res(TINFO, "cap_set_flag(CAP_SETGID) succeeded");
>> +
>> +	check_no_new_privs(0);
>> +
>> +	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");
>> +
>> +	check_no_new_privs(1);
>> +
>> +	if (tst_get_path("prctl06_execve", path, sizeof(path)))
>> +		tst_brk(TCONF, "Couldn't find prctl_execve in $PATH");
> If the path to the binary is in $PATH you don't have to execute the
> binary by an absolute path, passing it's name to execve() should
> suffice.
>
  It sounds reasonable. I will pass binary_name directly.

>> +	sprintf(ipc_env_var, IPC_ENV_VAR "=%s", getenv(IPC_ENV_VAR));
>> +	childpid = SAFE_FORK();
>> +	if (childpid == 0) {
>> +		check_no_new_privs(1);
> 		Maybe we can pass a "child" string here and "parent"
> 		string in the cases above so that we can print if the
> 		check was done in child/parent in the tst_res() inside
> 		of this function.
>
Ok. Pass a "child" or "parent" string can make it more clear for user.

>> +		execve(path, childargv, envp);
>> +		tst_brk(TFAIL | TERRNO,
>> +			"child process failed to execute prctl_execve");
>> +
>> +	} else {
>> +		tst_reap_children();
>> +		execve(path, argv, envp);
>> +		tst_brk(TFAIL | TERRNO,
>> +			"parent process failed to execute prctl_execve");
>> +	}
>> +	cap_free(caps);
>> +	return;
>> +}
>> +
>> +static void verify_prctl(void)
>> +{
>> +	int pid;
>> +
>> +	pid = SAFE_FORK();
>> +	if (pid == 0) {
>> +		do_prctl();
>> +		exit(0);
>> +	}
>> +	tst_reap_children();
>> +	return;
>> +}
>> +
>> +static struct tst_test test = {
>> +	.test_all = verify_prctl,
>> +	.forks_child = 1,
>> +	.needs_root = 1,
>> +	.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..84c28551c
>> --- /dev/null
>> +++ b/testcases/kernel/syscalls/prctl/prctl06_execve.c
>> @@ -0,0 +1,46 @@
>> +// 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 "tst_test.h"
>> +
>> +int main(int argc, char **argv)
>> +{
>> +	struct passwd *pw;
>> +	uid_t nobody_uid;
>> +	gid_t nobody_gid;
>> +
>> +	tst_reinit();
>> +	if (argc != 2)
>> +		tst_brk(TFAIL, "argc is %d, expected 2", argc);
>> +
>> +	pw = SAFE_GETPWNAM("nobody");
>> +	nobody_uid = pw->pw_uid;
>> +	nobody_gid = pw->pw_gid;
>> +	/*positive check*/
>> +	TEST(setgid(nobody_gid));
>> +	if (TST_RET == -1)
>> +		tst_res(TFAIL | TTERRNO,
>> +			"%s setgid(%d) isn't permmit", argv[1], nobody_gid);
>> +	else
>> +		tst_res(TPASS, "%s setgid(%d) succeed expectedly",
>> +			argv[1], nobody_gid);
>> +	/*negative check*/
>> +	TEST(setuid(nobody_uid));
>> +	if (TST_RET == -1)
>> +		tst_res(TPASS | TTERRNO,
>> +			"%s setuid(%d) isn't permmit", argv[1], nobody_uid);
>> +	else
>> +		tst_res(TFAIL, " %s setuid(%d) succeed unexpectedly",
>> +			argv[1], nobody_gid);
>> +	return 0;
>> +}
> I do not think that this is actually testing the prctl in question. Here
> we actually check that capabilities were inherited over fork() + exec().
>
> As far as I understand the PR_SET_NO_NEW_PRIVS it has been designed
> expecially to avoid misuse of capabilities associated with a particular
> file.
>
> So the check would have to do:
>
> 1. copy the prctl06_execve binary to a $PWD
> 2. chmod it with S_ISUID, S_ISGID
> 3. the prct06_execve would be executed under user/group nobody
> 4. the prct06_execve itself would check if it gained root priviledges
>
I have seen documention about PR_SET_NO_NEW_PRIVS.  I think your way is right. But I have reset this capabilities
and only keep CAP_SETGID, so when I set no_new_privs to 1, prctl06_execve inherits CAP_SETGID but not gain CAP_SETUID.


> And we can do the same for capablities as well.
>
> I guess that we would have to format a device and mount it with support
> for capabilities for the test, since as far as I can tell you cannot
> usually do neither of set-usr-id or add capabilites to files in /tmp/.
Yes. In /tmp, these make no sense. I will follow by your advise.

Thanks
Yang Xu
Cyril Hrubis July 9, 2019, 10:53 a.m. UTC | #4
Hi!
Can you pretty please resend the prctl06 v2 patch?

I can see it in the patchwork at
http://patchwork.ozlabs.org/patch/1118628/
but that email haven't made it to my mailbox.
Yang Xu July 10, 2019, 9:42 a.m. UTC | #5
> Hi!
> Can you pretty please resend the prctl06 v2 patch?
>
> I can see it in the patchwork at
> http://patchwork.ozlabs.org/patch/1118628/
> but that email haven't made it to my mailbox.
>
Hi cyril

I have resent it again.

ps:I sent it but email date is 2019/07/06 6:48 (my virtual machine date is incorrect I don't notice it )

Thanks
Yang Xu
diff mbox series

Patch

diff --git a/include/lapi/prctl.h b/include/lapi/prctl.h
index 91da9c2d6..663ce19e9 100644
--- a/include/lapi/prctl.h
+++ b/include/lapi/prctl.h
@@ -24,4 +24,9 @@ 
 # define PR_SET_SECCOMP  22
 #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 950615bef..a43bf5e4d 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -865,6 +865,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/Makefile b/testcases/kernel/syscalls/prctl/Makefile
index bd617d806..99a9d42e7 100644
--- a/testcases/kernel/syscalls/prctl/Makefile
+++ b/testcases/kernel/syscalls/prctl/Makefile
@@ -20,4 +20,6 @@  top_srcdir		?= ../../../..
 
 include $(top_srcdir)/include/mk/testcases.mk
 
+LDLIBS			+= $(CAP_LIBS)
+
 include $(top_srcdir)/include/mk/generic_leaf_target.mk
diff --git a/testcases/kernel/syscalls/prctl/prctl06.c b/testcases/kernel/syscalls/prctl/prctl06.c
new file mode 100644
index 000000000..ba5c2bcfb
--- /dev/null
+++ b/testcases/kernel/syscalls/prctl/prctl06.c
@@ -0,0 +1,117 @@ 
+// 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 (as the function result) 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, execve(2) promises not to grant privileges
+ *   to do anything that could not have been done without the execve(2)
+ *   call(for example, rendering the set-user-ID and set-group-ID mode bits)
+ * 3)The setting of this bit is inherited by children created by fork(2) and
+ *   clone(2), and preserved across execve(2).
+ */
+
+#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"
+
+static void check_no_new_privs(int val)
+{
+	TEST(prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0));
+	if (TST_RET == val)
+		tst_res(TPASS,
+			"prctl(PR_GET_NO_NEW_PRIVS) got %d "
+			"when no_new_privs was %d", val, val);
+	else
+		tst_res(TFAIL | TTERRNO,
+			"prctl(PR_GET_NO_NEW_PRIVS) expected %d got %ld",
+			val, TST_RET);
+	return;
+}
+
+static void do_prctl(void)
+{
+	char path[4096];
+	char ipc_env_var[1024];
+	char *const argv[] = {"prctl06_execve", "parent process", NULL};
+	char *const childargv[] = {"prctl06_execve", "child process", NULL};
+	char *const envp[] = {"LTP_TEST_ENV_VAR=test", ipc_env_var, NULL };
+	cap_t caps = cap_init();
+	cap_value_t capList = CAP_SETGID;
+	unsigned int num_caps = 1;
+	int childpid;
+
+	cap_set_flag(caps, CAP_EFFECTIVE, num_caps, &capList, CAP_SET);
+	cap_set_flag(caps, CAP_INHERITABLE, num_caps, &capList, CAP_SET);
+	cap_set_flag(caps, CAP_PERMITTED, num_caps, &capList, CAP_SET);
+
+	if (cap_set_proc(caps))
+		tst_brk(TFAIL | TERRNO,
+			"cap_set_flag(CAP_SETGID) failed");
+	tst_res(TINFO, "cap_set_flag(CAP_SETGID) succeeded");
+
+	check_no_new_privs(0);
+
+	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");
+
+	check_no_new_privs(1);
+
+	if (tst_get_path("prctl06_execve", path, sizeof(path)))
+		tst_brk(TCONF, "Couldn't find prctl_execve in $PATH");
+
+	sprintf(ipc_env_var, IPC_ENV_VAR "=%s", getenv(IPC_ENV_VAR));
+	childpid = SAFE_FORK();
+	if (childpid == 0) {
+		check_no_new_privs(1);
+		execve(path, childargv, envp);
+		tst_brk(TFAIL | TERRNO,
+			"child process failed to execute prctl_execve");
+
+	} else {
+		tst_reap_children();
+		execve(path, argv, envp);
+		tst_brk(TFAIL | TERRNO,
+			"parent process failed to execute prctl_execve");
+	}
+	cap_free(caps);
+	return;
+}
+
+static void verify_prctl(void)
+{
+	int pid;
+
+	pid = SAFE_FORK();
+	if (pid == 0) {
+		do_prctl();
+		exit(0);
+	}
+	tst_reap_children();
+	return;
+}
+
+static struct tst_test test = {
+	.test_all = verify_prctl,
+	.forks_child = 1,
+	.needs_root = 1,
+	.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..84c28551c
--- /dev/null
+++ b/testcases/kernel/syscalls/prctl/prctl06_execve.c
@@ -0,0 +1,46 @@ 
+// 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 "tst_test.h"
+
+int main(int argc, char **argv)
+{
+	struct passwd *pw;
+	uid_t nobody_uid;
+	gid_t nobody_gid;
+
+	tst_reinit();
+	if (argc != 2)
+		tst_brk(TFAIL, "argc is %d, expected 2", argc);
+
+	pw = SAFE_GETPWNAM("nobody");
+	nobody_uid = pw->pw_uid;
+	nobody_gid = pw->pw_gid;
+	/*positive check*/
+	TEST(setgid(nobody_gid));
+	if (TST_RET == -1)
+		tst_res(TFAIL | TTERRNO,
+			"%s setgid(%d) isn't permmit", argv[1], nobody_gid);
+	else
+		tst_res(TPASS, "%s setgid(%d) succeed expectedly",
+			argv[1], nobody_gid);
+	/*negative check*/
+	TEST(setuid(nobody_uid));
+	if (TST_RET == -1)
+		tst_res(TPASS | TTERRNO,
+			"%s setuid(%d) isn't permmit", argv[1], nobody_uid);
+	else
+		tst_res(TFAIL, " %s setuid(%d) succeed unexpectedly",
+			argv[1], nobody_gid);
+	return 0;
+}