daemon-helper: add read-file-as-user

Add a new command to read the content of a file after switching to the
given user. This command can be used to enforce Unix filesystem
permissions when accessing a file on behalf of a user.
This commit is contained in:
Beniamino Galvani 2025-09-29 09:52:51 +02:00 committed by Íñigo Huguet
parent 6c1e04fc61
commit 41e28b900f
4 changed files with 156 additions and 6 deletions

View file

@ -4,10 +4,14 @@
#include "nm-std-utils.h" #include "nm-std-utils.h"
#include <stdint.h>
#include <assert.h> #include <assert.h>
#include <fcntl.h>
#include <grp.h>
#include <limits.h> #include <limits.h>
#include <net/if.h> #include <net/if.h>
#include <pwd.h>
#include <stdint.h>
#include <sys/types.h>
/*****************************************************************************/ /*****************************************************************************/
@ -95,6 +99,114 @@ out_huge:
/*****************************************************************************/ /*****************************************************************************/
bool
nm_utils_set_effective_user(const char *user, char *errbuf, size_t errbuf_len)
{
struct passwd *pwentry;
int errsv;
char error[1024];
errno = 0;
pwentry = getpwnam(user);
if (!pwentry) {
errsv = errno;
if (errsv == 0) {
snprintf(errbuf, errbuf_len, "user not found");
} else {
snprintf(errbuf,
errbuf_len,
"error getting user entry: %d (%s)\n",
errsv,
strerror_r(errsv, error, sizeof(error)));
}
return false;
}
if (setgid(pwentry->pw_gid) != 0) {
errsv = errno;
snprintf(errbuf,
errbuf_len,
"failed to change group to %u: %d (%s)\n",
pwentry->pw_gid,
errsv,
strerror_r(errsv, error, sizeof(error)));
return false;
}
if (initgroups(user, pwentry->pw_gid) != 0) {
errsv = errno;
snprintf(errbuf,
errbuf_len,
"failed to reset supplementary group list to %u: %d (%s)\n",
pwentry->pw_gid,
errsv,
strerror_r(errsv, error, sizeof(error)));
return false;
}
if (setuid(pwentry->pw_uid) != 0) {
errsv = errno;
snprintf(errbuf,
errbuf_len,
"failed to change user to %u: %d (%s)\n",
pwentry->pw_uid,
errsv,
strerror_r(errsv, error, sizeof(error)));
return false;
}
return true;
}
/*****************************************************************************/
bool
nm_utils_read_file_to_stdout(const char *filename, char *errbuf, size_t errbuf_len)
{
nm_auto_close int fd = -1;
char buffer[4096];
char error[1024];
ssize_t bytes_read;
int errsv;
fd = open(filename, O_RDONLY);
if (fd == -1) {
errsv = errno;
snprintf(errbuf,
errbuf_len,
"error opening the file: %d (%s)",
errsv,
strerror_r(errsv, error, sizeof(error)));
return false;
}
while ((bytes_read = read(fd, buffer, sizeof(buffer))) > 0) {
if (fwrite(buffer, 1, bytes_read, stdout) != (size_t) bytes_read) {
errsv = errno;
snprintf(errbuf,
errbuf_len,
"error writing to stdout: %d (%s)",
errsv,
strerror_r(errsv, error, sizeof(error)));
return false;
}
}
if (bytes_read < 0) {
errsv = errno;
snprintf(errbuf,
errbuf_len,
"error reading the file: %d (%s)",
errsv,
strerror_r(errsv, error, sizeof(error)));
return false;
}
return true;
}
/*****************************************************************************/
/** /**
* _nm_strerror_r: * _nm_strerror_r:
* @errsv: the errno passed to strerror_r() * @errsv: the errno passed to strerror_r()

View file

@ -37,4 +37,8 @@ size_t nm_utils_get_next_realloc_size(bool true_realloc, size_t requested);
const char *_nm_strerror_r(int errsv, char *buf, size_t buf_size); const char *_nm_strerror_r(int errsv, char *buf, size_t buf_size);
bool nm_utils_set_effective_user(const char *user, char *errbuf, size_t errbuf_size);
bool nm_utils_read_file_to_stdout(const char *filename, char *errbuf, size_t errbuf_len);
#endif /* __NM_STD_UTILS_H__ */ #endif /* __NM_STD_UTILS_H__ */

View file

@ -7,12 +7,13 @@ components.
nm-daemon-helper nm-daemon-helper
---------------- ----------------
A internal helper application that is spawned by NetworkManager A internal helper application that is spawned by NetworkManager to
to perform certain actions. perform certain actions which can't be done in the daemon.
Currently all it does is doing a reverse DNS lookup, which Currently it's used to do a reverse DNS lookup after reconfiguring the
cannot be done by NetworkManager because the operation requires libc resolver (which is a process-wide operation), and to read files
to reconfigure the libc resolver (which is a process-wide operation). on behalf of unprivileged users (which requires a seteuid that affects
all the threads of the process).
This is not directly useful to the user. This is not directly useful to the user.

View file

@ -137,6 +137,37 @@ cmd_resolve_address(void)
return RETURN_ERROR; return RETURN_ERROR;
} }
static int
cmd_read_file_as_user(void)
{
nm_auto_free char *user = NULL;
nm_auto_free char *filename = NULL;
char error[1024];
user = read_arg();
if (!user)
return RETURN_INVALID_ARGS;
filename = read_arg();
if (!filename)
return RETURN_INVALID_ARGS;
if (more_args())
return RETURN_INVALID_ARGS;
if (!nm_utils_set_effective_user(user, error, sizeof(error))) {
fprintf(stderr, "Failed to set effective user '%s': %s", user, error);
return RETURN_ERROR;
}
if (!nm_utils_read_file_to_stdout(filename, error, sizeof(error))) {
fprintf(stderr, "Failed to read file '%s' as user '%s': %s", filename, user, error);
return RETURN_ERROR;
}
return RETURN_SUCCESS;
}
int int
main(int argc, char **argv) main(int argc, char **argv)
{ {
@ -150,6 +181,8 @@ main(int argc, char **argv)
return cmd_version(); return cmd_version();
if (nm_streq(cmd, "resolve-address")) if (nm_streq(cmd, "resolve-address"))
return cmd_resolve_address(); return cmd_resolve_address();
if (nm_streq(cmd, "read-file-as-user"))
return cmd_read_file_as_user();
return RETURN_INVALID_CMD; return RETURN_INVALID_CMD;
} }