module-access: determine trusted application executable

Determine application executable file so that the result can be trusted,
and the file exists in the current namespace.

Don't use /proc/pid/cmdline, since that contains whatever was specified
by the exec() call.
This commit is contained in:
Pauli Virtanen 2022-08-13 19:08:52 +03:00 committed by Wim Taymans
parent 64235419a6
commit 0e831c52d8

View file

@ -131,41 +131,57 @@ struct impl {
struct spa_hook module_listener; struct spa_hook module_listener;
}; };
static int check_cmdline(struct pw_impl_client *client, int pid, const char *str) static int get_exe_name(int pid, char *buf, size_t buf_size)
{ {
char path[2048], key[1024]; char path[256];
ssize_t len; struct stat s1, s2;
int fd, res; int res;
/*
* Find executable name, checking it is an existing file
* (in the current namespace).
*/
#if defined(__linux__)
spa_scnprintf(path, sizeof(path), "/proc/%u/exe", pid);
#elif defined(__FreeBSD__) || defined(__MidnightBSD__)
spa_scnprintf(path, sizeof(path), "/proc/%u/file", pid);
#else
return -ENOTSUP;
#endif
res = readlink(path, buf, buf_size);
if (res < 0)
return -errno;
if ((size_t)res >= buf_size)
return -E2BIG;
buf[res] = '\0';
/* Check the file exists (= not deleted, and is in current namespace) */
if (stat(path, &s1) != 0 || stat(buf, &s2) != 0)
return -errno;
if (s1.st_dev != s2.st_dev || s1.st_ino != s2.st_ino)
return -ENXIO;
return 0;
}
static int check_exe(struct pw_impl_client *client, const char *path, const char *str)
{
char key[1024];
int res;
struct spa_json it[2]; struct spa_json it[2];
sprintf(path, "/proc/%u/cmdline", pid);
fd = open(path, O_RDONLY);
if (fd < 0) {
res = -errno;
goto exit;
}
if ((len = read(fd, path, sizeof(path)-1)) < 0) {
res = -errno;
goto exit_close;
}
path[len] = '\0';
spa_json_init(&it[0], str, strlen(str)); spa_json_init(&it[0], str, strlen(str));
if ((res = spa_json_enter_array(&it[0], &it[1])) <= 0) if ((res = spa_json_enter_array(&it[0], &it[1])) <= 0)
goto exit_close; return res;
while (spa_json_get_string(&it[1], key, sizeof(key)) > 0) { while (spa_json_get_string(&it[1], key, sizeof(key)) > 0) {
if (spa_streq(path, key)) { if (spa_streq(path, key))
res = 1; return 1;
goto exit_close;
}
} }
res = 0;
exit_close: return 0;
close(fd);
exit:
return res;
} }
static void static void
@ -174,6 +190,7 @@ context_check_access(void *data, struct pw_impl_client *client)
struct impl *impl = data; struct impl *impl = data;
struct pw_permission permissions[1]; struct pw_permission permissions[1];
struct spa_dict_item items[2]; struct spa_dict_item items[2];
char exe_path[PATH_MAX];
const struct pw_properties *props; const struct pw_properties *props;
const char *str, *access; const char *str, *access;
char *flatpak_app_id = NULL; char *flatpak_app_id = NULL;
@ -195,10 +212,17 @@ context_check_access(void *data, struct pw_impl_client *client)
goto granted; goto granted;
} else { } else {
pw_log_info("client %p has trusted pid %d", client, pid); pw_log_info("client %p has trusted pid %d", client, pid);
if ((res = get_exe_name(pid, exe_path, sizeof(exe_path))) >= 0) {
pw_log_info("client %p has trusted exe path '%s'", client, exe_path);
} else {
pw_log_info("client %p has no trusted exe path: %s",
client, spa_strerror(res));
exe_path[0] = '\0';
}
} }
if (impl->properties && (str = pw_properties_get(impl->properties, "access.allowed")) != NULL) { if (impl->properties && (str = pw_properties_get(impl->properties, "access.allowed")) != NULL) {
res = check_cmdline(client, pid, str); res = check_exe(client, exe_path, str);
if (res < 0) { if (res < 0) {
pw_log_warn("%p: client %p allowed check failed: %s", pw_log_warn("%p: client %p allowed check failed: %s",
impl, client, spa_strerror(res)); impl, client, spa_strerror(res));
@ -209,7 +233,7 @@ context_check_access(void *data, struct pw_impl_client *client)
} }
if (impl->properties && (str = pw_properties_get(impl->properties, "access.rejected")) != NULL) { if (impl->properties && (str = pw_properties_get(impl->properties, "access.rejected")) != NULL) {
res = check_cmdline(client, pid, str); res = check_exe(client, exe_path, str);
if (res < 0) { if (res < 0) {
pw_log_warn("%p: client %p rejected check failed: %s", pw_log_warn("%p: client %p rejected check failed: %s",
impl, client, spa_strerror(res)); impl, client, spa_strerror(res));
@ -221,7 +245,7 @@ context_check_access(void *data, struct pw_impl_client *client)
} }
if (impl->properties && (str = pw_properties_get(impl->properties, "access.restricted")) != NULL) { if (impl->properties && (str = pw_properties_get(impl->properties, "access.restricted")) != NULL) {
res = check_cmdline(client, pid, str); res = check_exe(client, exe_path, str);
if (res < 0) { if (res < 0) {
pw_log_warn("%p: client %p restricted check failed: %s", pw_log_warn("%p: client %p restricted check failed: %s",
impl, client, spa_strerror(res)); impl, client, spa_strerror(res));