Skip to contentSkip to conversation

read_all: Drop privileges

PATCH Superseded Richard Palethorpe 5 participants 17 messages May 15, 2018 – May 22, 2018

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 read_all: Drop privileges

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.

---
 runtest/fs                              |  2 +-
 testcases/kernel/fs/read_all/read_all.c | 26 +++++++++++++++++++++++++-
 2 files changed, 26 insertions(+), 2 deletions(-)
runtest/fs +1 -1
index 42a9bfcbf..a66948a43 100644
@@ -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
 
testcases/kernel/fs/read_all/read_all.c +25 -1
index add3651c8..9c632c009 100644
@@ -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));
+		}
 	}
 }
 
Signature · 1 line
2.16.3
#1 lore raw

PATCH 1/1 read_all: Drop privileges

Hi!

testcases/kernel/fs/read_all/read_all.c static int worker_run(struct worker *self) · line 265

Quoted 12 lines
testcases/kernel/fs/read_all/read_all.c +12 -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");

^
Shouldn't this be tst_brk()?

testcases/kernel/fs/read_all/read_all.c static int worker_run(struct worker *self) · line 269

Quoted 3 lines
testcases/kernel/fs/read_all/read_all.c +3 -0
+	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.

Signature · 2 lines
Cyril Hrubis
[email protected]
#2 lore raw

PATCH 1/1 read_all: Drop privileges

Hello,

Quoted 16 lines · Cyril Hrubis

Hi!

testcases/kernel/fs/read_all/read_all.c static int worker_run(struct worker *self) · line 265

Quoted 12 lines
testcases/kernel/fs/read_all/read_all.c +12 -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");

^
Shouldn't this be tst_brk()?

Quoted 7 lines

testcases/kernel/fs/read_all/read_all.c static int worker_run(struct worker *self) · line 269

Quoted 3 lines
testcases/kernel/fs/read_all/read_all.c +3 -0
+	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.

Signature · 2 lines
Thank you,
Richard.
#3 lore raw

PATCH 1/1 read_all: Drop privileges

Hi!

Quoted 3 lines
Quoted 1 line

Otherwise it looks fine.

Well spotted, yes it should.

I will push it with that change then, thanks.

Signature · 2 lines
Cyril Hrubis
[email protected]
#4 lore raw

PATCH 1/1 read_all: Drop privileges

Hi Richard, Cyril,

Quoted 6 lines · Cyril Hrubis

Hi!

Quoted 3 lines
Quoted 1 line

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

#5 lore raw

PATCH 1/1 read_all: Drop privileges

Hello,

Quoted 9 lines · Punit Agrawal

Hi Richard, Cyril,

Quoted 5 lines · Cyril Hrubis

Hi!

Quoted 3 lines
Quoted 1 line

Otherwise it looks fine.

Well spotted, yes it should.

Quoted 8 lines
Quoted 1 line

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.

Signature · 2 lines
Thank you,
Richard.
#6 lore raw

PATCH 1/1 read_all: Drop privileges

In reply to Cyril Hrubis

Hi Richard, Cyril,

Quoted 6 lines · Cyril Hrubis

Hi!

Quoted 3 lines
Quoted 1 line

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

#7 lore raw

PATCH 1/1 read_all: Drop privileges

In reply to Richard Palethorpe

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

Quoted 29 lines · Richard Palethorpe

Hello,

Quoted 16 lines · Cyril Hrubis

Hi!

testcases/kernel/fs/read_all/read_all.c static int worker_run(struct worker *self) · line 265

Quoted 12 lines
testcases/kernel/fs/read_all/read_all.c +12 -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");

^
Shouldn't this be tst_brk()?

Quoted 7 lines

testcases/kernel/fs/read_all/read_all.c static int worker_run(struct worker *self) · line 269

Quoted 3 lines
testcases/kernel/fs/read_all/read_all.c +3 -0
+	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.

#8 lore raw

PATCH 1/1 read_all: Drop privileges

Hi!

Quoted 2 lines

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.

Signature · 2 lines
Cyril Hrubis
[email protected]
#9 lore raw

PATCH 1/1 read_all: Drop privileges

Quoted 4 lines · Cyril Hrubis

Hi!

Quoted 2 lines

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

#10 lore raw

PATCH 1/1 read_all: Drop privileges

Hi!

Quoted 1 line

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.

Signature · 2 lines
Cyril Hrubis
[email protected]
#11 lore raw

PATCH 1/1 read_all: Drop privileges

Quoted 16 lines · Cyril Hrubis

Hi!

Quoted 1 line

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.

Quoted 4 lines

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

#12 lore raw

PATCH 1/1 fs/read_all: Clear suplementary groups before droping privileges

In reply to Cyril Hrubis

Current user(e.g. root) has its own suplementary group set when logged in. Which means that even when a program sets it's user and group ids to nobody the current group still stays in the list of supplementary groups, which then is matched for files with the current group ownership and hence we can still access the file.

For example, if /dev/watchdog has root group ownership and rw group permissions, running read_all_dev can still open /dev/watchdog and reboot system even after switching user and group ids from root to nobody.

We need to clear suplementary groups before droping privileges and keep the same rule as commit 1f011e5 if current user doesn't have the capabilities to clear suplementary groups.

---
 testcases/kernel/fs/read_all/read_all.c | 6 ++++++
 1 file changed, 6 insertions(+)
testcases/kernel/fs/read_all/read_all.c +6 -0
index a8e1611..acd8e73 100644
@@ -258,6 +258,12 @@ static void maybe_drop_privs(void)
 	if (!drop_privs)
 		return;
 
+	TEST(setgroups(0, NULL));
+	if (TEST_RETURN < 0 && TEST_ERRNO != EPERM) {
+		tst_brk(TBROK | TTERRNO,
+			"Failed to clear suplementary group set");
+	}
+
 	nobody = SAFE_GETPWNAM("nobody");
 
 	TEST(setgid(nobody->pw_gid));
Signature · 1 line
1.8.3.1
#13 lore raw

PATCH 1/1 fs/read_all: Clear suplementary groups before droping privileges

Hello,

Quoted 25 lines · Xiao Yang

Current user(e.g. root) has its own suplementary group set when logged in. Which means that even when a program sets it's user and group ids to nobody the current group still stays in the list of supplementary groups, which then is matched for files with the current group ownership and hence we can still access the file.

For example, if /dev/watchdog has root group ownership and rw group permissions, running read_all_dev can still open /dev/watchdog and reboot system even after switching user and group ids from root to nobody.

We need to clear suplementary groups before droping privileges and keep the same rule as commit 1f011e5 if current user doesn't have the capabilities to clear suplementary groups.

---
 testcases/kernel/fs/read_all/read_all.c | 6 ++++++
 1 file changed, 6 insertions(+)
testcases/kernel/fs/read_all/read_all.c +0 -0
index a8e1611..acd8e73 100644
@@ -258,6 +258,12 @@ static void maybe_drop_privs(void)
 	if (!drop_privs)
 		return;

testcases/kernel/fs/read_all/read_all.c static void maybe_drop_privs(void) · line 269

Quoted 10 lines
testcases/kernel/fs/read_all/read_all.c +6 -0
 
+	TEST(setgroups(0, NULL));
+	if (TEST_RETURN < 0 && TEST_ERRNO != EPERM) {
+		tst_brk(TBROK | TTERRNO,
+			"Failed to clear suplementary group set");
+	}
+
 	nobody = SAFE_GETPWNAM("nobody");
 
 	TEST(setgid(nobody->pw_gid));

LGTM!

Signature · 2 lines
Thank you,
Richard.
#14 lore raw

PATCH 1/1 fs/read_all: Clear suplementary groups before droping privileges

Hi!

Quoted 1 line

LGTM!

We may as well think about safe groups for the program, looking at my /dev/ it may sense to add auxiliary groups disk and tty so that we tests these devices as well.

Signature · 2 lines
Cyril Hrubis
[email protected]
#15 lore raw

PATCH 1/1 fs/read_all: Clear suplementary groups before droping privileges

In reply to Xiao Yang

Hi!
I've added #include <grp.h> to the patch so that we get definition for the setgrp() call and pushed, thanks.

Signature · 2 lines
Cyril Hrubis
[email protected]
#16 lore raw

PATCH v2 1/1 read_all: Drop privileges

In reply to Cyril Hrubis

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.

---
 runtest/fs                              |  2 +-
 testcases/kernel/fs/read_all/read_all.c | 26 +++++++++++++++++++++++++-
 2 files changed, 26 insertions(+), 2 deletions(-)
runtest/fs +1 -1
index 42a9bfcbf..a66948a43 100644
@@ -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
 
testcases/kernel/fs/read_all/read_all.c +25 -1
index add3651c8..a8e161129 100644
@@ -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_brk(TBROK | TTERRNO, "Failed to use nobody gid");
+
+	TEST(setuid(nobody->pw_uid));
+	if (TEST_RETURN < 0 && TEST_ERRNO != EPERM)
+		tst_brk(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));
+		}
 	}
 }
 
Signature · 1 line
2.16.3