diff mbox series

read_all: Drop privileges

Message ID 20180515095118.26282-1-rpalethorpe@suse.com
State Changes Requested
Delegated to: Cyril Hrubis
Headers show
Series read_all: Drop privileges | expand

Commit Message

Richard Palethorpe May 15, 2018, 9:51 a.m. UTC
The LTP is usually run as root, which allows read_all_dev to read files which
are usually protected from being read at random. This patch introduces the -p
switch to read_all which is used to drop privileges (switch to the nobody
user) for the read_all_dev test.

If -p is set, but the current user does not have the capabilities to change
the uid and gid, then the test will continue under the current user. This
allows the most common scenarios to work as expected, but may cause
difficulties for someone running the LTP under a semi-privileged user.

Signed-off-by: Richard Palethorpe <rpalethorpe@suse.com>
---
 runtest/fs                              |  2 +-
 testcases/kernel/fs/read_all/read_all.c | 26 +++++++++++++++++++++++++-
 2 files changed, 26 insertions(+), 2 deletions(-)

Comments

Cyril Hrubis May 15, 2018, 10:30 a.m. UTC | #1
Hi!
> +static void maybe_drop_privs(void)
> +{
> +	struct passwd *nobody;
> +
> +	if (!drop_privs)
> +		return;
> +
> +	nobody = SAFE_GETPWNAM("nobody");
> +
> +	TEST(setgid(nobody->pw_gid));
> +	if (TEST_RETURN < 0 && TEST_ERRNO != EPERM)
> +		tst_res(TBROK | TTERRNO, "Failed to use nobody gid");
                ^
		Shouldn't this be tst_brk()?

> +	TEST(setuid(nobody->pw_uid));
> +	if (TEST_RETURN < 0 && TEST_ERRNO != EPERM)
> +		tst_res(TBROK | TTERRNO, "Failed to use nobody uid");
                ^
		And here as well?

Otherwise it looks fine.
Richard Palethorpe May 15, 2018, 10:55 a.m. UTC | #2
Hello,

Cyril Hrubis writes:

> Hi!
>> +static void maybe_drop_privs(void)
>> +{
>> +	struct passwd *nobody;
>> +
>> +	if (!drop_privs)
>> +		return;
>> +
>> +	nobody = SAFE_GETPWNAM("nobody");
>> +
>> +	TEST(setgid(nobody->pw_gid));
>> +	if (TEST_RETURN < 0 && TEST_ERRNO != EPERM)
>> +		tst_res(TBROK | TTERRNO, "Failed to use nobody gid");
>                 ^
> 		Shouldn't this be tst_brk()?
>
>> +	TEST(setuid(nobody->pw_uid));
>> +	if (TEST_RETURN < 0 && TEST_ERRNO != EPERM)
>> +		tst_res(TBROK | TTERRNO, "Failed to use nobody uid");
>                 ^
> 		And here as well?
>
> Otherwise it looks fine.

Well spotted, yes it should.
Cyril Hrubis May 15, 2018, 10:57 a.m. UTC | #3
Hi!
> > Otherwise it looks fine.
> 
> Well spotted, yes it should.

I will push it with that change then, thanks.
Punit Agrawal May 15, 2018, 11:18 a.m. UTC | #4
Hi Richard, Cyril,

Cyril Hrubis <chrubis@suse.cz> writes:

> Hi!
>> > Otherwise it looks fine.
>> 
>> Well spotted, yes it should.
>
> I will push it with that change then, thanks.

I can confirm that with the update, the system no longer generates
aborts due to /dev/port access.

Thanks for the quick turn around.

Punit
Punit Agrawal May 15, 2018, 11:23 a.m. UTC | #5
Hi Richard, Cyril,

Cyril Hrubis <chrubis@suse.cz> writes:

> Hi!
>> > Otherwise it looks fine.
>> 
>> Well spotted, yes it should.
>
> I will push it with that change then, thanks.

I can confirm that with the change the system no longer generates aborts
while reading nodes in /dev.

Thanks for pushing this through before the release.

Punit
Richard Palethorpe May 15, 2018, 12:34 p.m. UTC | #6
Hello,

Punit Agrawal writes:

> Hi Richard, Cyril,
>
> Cyril Hrubis <chrubis@suse.cz> writes:
>
>> Hi!
>>> > Otherwise it looks fine.
>>> 
>>> Well spotted, yes it should.
>>
>> I will push it with that change then, thanks.
>
> I can confirm that with the update, the system no longer generates
> aborts due to /dev/port access.
>
> Thanks for the quick turn around.
>
> Punit

Great, thanks for testing it.
Xiao Yang May 16, 2018, 9:39 a.m. UTC | #7
Hi Richard,

If the permission of /dev/watchdog was 0600(default permission on RHEL7), we could not read /dev/watchdog
as nobody user and returned EACCES as expected.

If the permission of /dev/watchdog was 0660(default permission on RHEL6), Reading /dev/watchdog as nobody
user failed, but still led to system reboot.

I think reading /dev/watchdog as nobody user should get EACCES even if the permission is 0660, but i am not
sure whether this is a watchdog bug in kernel or not.

Thanks,
Xiao Yang

On 2018/05/15 18:55, Richard Palethorpe wrote:
> Hello,
>
> Cyril Hrubis writes:
>
>> Hi!
>>> +static void maybe_drop_privs(void)
>>> +{
>>> +	struct passwd *nobody;
>>> +
>>> +	if (!drop_privs)
>>> +		return;
>>> +
>>> +	nobody = SAFE_GETPWNAM("nobody");
>>> +
>>> +	TEST(setgid(nobody->pw_gid));
>>> +	if (TEST_RETURN<  0&&  TEST_ERRNO != EPERM)
>>> +		tst_res(TBROK | TTERRNO, "Failed to use nobody gid");
>>                  ^
>> 		Shouldn't this be tst_brk()?
>>
>>> +	TEST(setuid(nobody->pw_uid));
>>> +	if (TEST_RETURN<  0&&  TEST_ERRNO != EPERM)
>>> +		tst_res(TBROK | TTERRNO, "Failed to use nobody uid");
>>                  ^
>> 		And here as well?
>>
>> Otherwise it looks fine.
> Well spotted, yes it should.
>
Cyril Hrubis May 16, 2018, 11:44 a.m. UTC | #8
Hi!
> If the permission of /dev/watchdog was 0660(default permission on RHEL6), Reading /dev/watchdog as nobody
> user failed, but still led to system reboot.

If unprivileged user can reboot the system it's a bug.
Xiao Yang May 17, 2018, 10:20 a.m. UTC | #9
On 2018/05/16 19:44, Cyril Hrubis wrote:
> Hi!
>> If the permission of /dev/watchdog was 0660(default permission on RHEL6), Reading /dev/watchdog as nobody
>> user failed, but still led to system reboot.
> If unprivileged user can reboot the system it's a bug.
Hi Cyril,

Sorry, it seems a bug in open(2) instead of watchdog.

You can reproduce the issue by running the following test.c:
----------------------------------------------------------------------------------------------------------
#include <errno.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <unistd.h>
#include <stdlib.h>

static void switch_privs(void)
{
         struct passwd *nobody;
         int ret;

         nobody = getpwnam("nobody");
         if (nobody == NULL) {
                 printf("getpwnam(nobody) failed with errno %d\n", errno);
                 exit(1);
         }

         ret = setgid(nobody->pw_gid);
         if (ret < 0) {
                 printf("Failed to use nobody gid with errno %d\n", errno);
                 exit(1);
         }

         ret = setuid(nobody->pw_uid);
         if (ret < 0) {
                 printf("Failed to use nobody uid with errno %d\n", errno);
                 exit(1);
         }
}

int main(void)
{
         int fd;

         umask(0);

         fd = open("testfile", O_RDWR | O_CREAT, 0660);
         if (fd < 0) {
                 printf("open(testfile) failed with errno %d\n", errno);
                 return 1;
         }

         close(fd);

         switch_privs();

         fd = open("testfile", O_RDWR);
         if (fd < 0) {
                 printf("open(testfile) failed with errno %d\n", errno);
                 return 1;
         }

         printf("open(testfile) succeeded unexpectedly\n");

         close(fd);
}
------------------------------------------------------------------------------------------------------------
# gcc -o test test.c
# ./test
open(testfile) succeeded unexpectedly

We created a test file with 0660 mode as root user, and opened the test 
file as nobody user switched by setuid() and setgid().
Running this test got success rather than EACCES.  Do you think this is 
a bug or i misunderstand the permissions of file?

Thanks,
Xiao Yang
Cyril Hrubis May 18, 2018, 5:09 p.m. UTC | #10
Hi!
> Sorry, it seems a bug in open(2) instead of watchdog.

Looks like the list of supplementary groups is at fault here.

On my system I do have in /etc/group:

root:x:0:root

Which means that among other groups root has root suplementary group set
when logged in.

Which means that even when a program sets it's user and group ids to
nobody the root still stays in the list of supplementary groups, which
then is matched for files with root group ownership and hence we can
stil open the file.

Adding setgroups(0, NULL); to switch_privs() in your program "fixes" the
behavior and we get EPERM as expected. And I guess that we should patch
the read_all to do the same, which should fix your problem. I will apply
the fix.
Xiao Yang May 19, 2018, 9:04 a.m. UTC | #11
On 2018/05/19 1:09, Cyril Hrubis wrote:
> Hi!
>> Sorry, it seems a bug in open(2) instead of watchdog.
> Looks like the list of supplementary groups is at fault here.
>
> On my system I do have in /etc/group:
>
> root:x:0:root
>
> Which means that among other groups root has root suplementary group set
> when logged in.
>
> Which means that even when a program sets it's user and group ids to
> nobody the root still stays in the list of supplementary groups, which
> then is matched for files with root group ownership and hence we can
> stil open the file.
>
> Adding setgroups(0, NULL); to switch_privs() in your program "fixes" the
> behavior and we get EPERM as expected. And I guess that we should patch
> the read_all to do the same, which should fix your problem. I will apply
> the fix.
Hi Cyril,

Thanks for your detailed explanation.
I will send the fix patch as you suggested.

Thanks,
Xiao Yang
diff mbox series

Patch

diff --git a/runtest/fs b/runtest/fs
index 42a9bfcbf..a66948a43 100644
--- a/runtest/fs
+++ b/runtest/fs
@@ -69,7 +69,7 @@  fs_di fs_di -d $TMPDIR
 # Was not sure why it should reside in runtest/crashme and won´t get tested ever
 proc01 proc01 -m 128
 
-read_all_dev read_all -d /dev -e '/dev/watchdog?(0)' -q -r 10
+read_all_dev read_all -d /dev -p -q -r 10
 read_all_proc read_all -d /proc -q -r 10
 read_all_sys read_all -d /sys -q -r 10
 
diff --git a/testcases/kernel/fs/read_all/read_all.c b/testcases/kernel/fs/read_all/read_all.c
index add3651c8..9c632c009 100644
--- a/testcases/kernel/fs/read_all/read_all.c
+++ b/testcases/kernel/fs/read_all/read_all.c
@@ -50,6 +50,7 @@ 
 #include <fnmatch.h>
 #include <semaphore.h>
 #include <ctype.h>
+#include <pwd.h>
 
 #include "tst_test.h"
 
@@ -88,6 +89,7 @@  static long worker_count;
 static char *str_max_workers;
 static long max_workers = 15;
 static struct worker *workers;
+static char *drop_privs;
 
 static struct tst_option options[] = {
 	{"v", &verbose,
@@ -104,6 +106,8 @@  static struct tst_option options[] = {
 	 "-w count Set the worker count limit, the default is 15."},
 	{"W:", &str_worker_count,
 	 "-W count Override the worker count. Ignores (-w) and the processor count."},
+	{"p", &drop_privs,
+	 "-p       Drop privileges; switch to the nobody user."},
 	{NULL, NULL, NULL}
 };
 
@@ -247,6 +251,24 @@  static int worker_run(struct worker *self)
 	return 0;
 }
 
+static void maybe_drop_privs(void)
+{
+	struct passwd *nobody;
+
+	if (!drop_privs)
+		return;
+
+	nobody = SAFE_GETPWNAM("nobody");
+
+	TEST(setgid(nobody->pw_gid));
+	if (TEST_RETURN < 0 && TEST_ERRNO != EPERM)
+		tst_res(TBROK | TTERRNO, "Failed to use nobody gid");
+
+	TEST(setuid(nobody->pw_uid));
+	if (TEST_RETURN < 0 && TEST_ERRNO != EPERM)
+		tst_res(TBROK | TTERRNO, "Failed to use nobody uid");
+}
+
 static void spawn_workers(void)
 {
 	int i;
@@ -257,8 +279,10 @@  static void spawn_workers(void)
 	for (i = 0; i < worker_count; i++) {
 		wa[i].q = queue_init();
 		wa[i].pid = SAFE_FORK();
-		if (!wa[i].pid)
+		if (!wa[i].pid) {
+			maybe_drop_privs();
 			exit(worker_run(wa + i));
+		}
 	}
 }