mirror of
https://github.com/flatpak/flatpak.git
synced 2026-05-24 00:41:00 -04:00
tests: Add comprehensive tests for glnx_chaseat
This commit is contained in:
@@ -34,6 +34,7 @@ if get_option('tests')
|
||||
|
||||
test_names = [
|
||||
'backports',
|
||||
'chase',
|
||||
'errors',
|
||||
'fdio',
|
||||
'macros',
|
||||
|
||||
406
tests/test-libglnx-chase.c
Normal file
406
tests/test-libglnx-chase.c
Normal file
@@ -0,0 +1,406 @@
|
||||
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
|
||||
*
|
||||
* Copyright (C) 2026 Red Hat, Inc.
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "libglnx-config.h"
|
||||
#include "libglnx.h"
|
||||
#include <glib.h>
|
||||
#include <stdlib.h>
|
||||
#include <gio/gio.h>
|
||||
#include <err.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "libglnx-testlib.h"
|
||||
|
||||
#define GLNX_CHASE_DEBUG_NO_OPENAT2 (1 << 31)
|
||||
#define GLNX_CHASE_DEBUG_NO_OPEN_TREE (1 << 30)
|
||||
|
||||
const char *test_paths[] = {
|
||||
"file/baz",
|
||||
"file/baz/",
|
||||
"file/baz/.",
|
||||
"file/baz/../baz",
|
||||
"file////baz/..//baz",
|
||||
"file////baz/..//../file/baz",
|
||||
};
|
||||
|
||||
static ino_t
|
||||
get_ino (int fd)
|
||||
{
|
||||
int r;
|
||||
struct stat st;
|
||||
|
||||
r = fstatat (fd, "", &st, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
|
||||
g_assert_cmpint (r, >=, 0);
|
||||
|
||||
return st.st_ino;
|
||||
}
|
||||
|
||||
static ino_t
|
||||
path_get_ino (const char *path)
|
||||
{
|
||||
int r;
|
||||
struct stat st;
|
||||
|
||||
r = fstatat (AT_FDCWD, path, &st, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
|
||||
g_assert_cmpint (r, >=, 0);
|
||||
|
||||
return st.st_ino;
|
||||
}
|
||||
|
||||
static char *
|
||||
get_abspath (int dfd,
|
||||
const char *path)
|
||||
{
|
||||
g_autofree char *proc_fd_path = NULL;
|
||||
g_autofree char *abs = NULL;
|
||||
g_autoptr(GError) error = NULL;
|
||||
|
||||
proc_fd_path = g_strdup_printf ("/proc/self/fd/%d", dfd);
|
||||
abs = glnx_readlinkat_malloc (AT_FDCWD, proc_fd_path, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_nonnull (abs);
|
||||
|
||||
return g_strdup_printf ("%s/%s", abs, path);
|
||||
}
|
||||
|
||||
static void
|
||||
check_chase (int dfd,
|
||||
const char *path,
|
||||
GlnxChaseFlags flags,
|
||||
int expected_ino)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
glnx_autofd int chase_fd = -1;
|
||||
|
||||
/* let's try to test the openat2 impl */
|
||||
chase_fd = glnx_chaseat (dfd, path, flags, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_cmpint (chase_fd, >=, 0);
|
||||
g_assert_cmpint (get_ino (chase_fd), ==, expected_ino);
|
||||
g_clear_fd (&chase_fd, NULL);
|
||||
|
||||
/* let's try to test the open_tree impl */
|
||||
chase_fd = glnx_chaseat (dfd, path,
|
||||
flags | GLNX_CHASE_DEBUG_NO_OPENAT2,
|
||||
&error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_cmpint (chase_fd, >=, 0);
|
||||
g_assert_cmpint (get_ino (chase_fd), ==, expected_ino);
|
||||
g_clear_fd (&chase_fd, NULL);
|
||||
|
||||
/* let's try to test the openat impl */
|
||||
chase_fd = glnx_chaseat (dfd, path,
|
||||
flags |
|
||||
GLNX_CHASE_DEBUG_NO_OPENAT2 |
|
||||
GLNX_CHASE_DEBUG_NO_OPEN_TREE,
|
||||
&error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_cmpint (chase_fd, >=, 0);
|
||||
g_assert_cmpint (get_ino (chase_fd), ==, expected_ino);
|
||||
g_clear_fd (&chase_fd, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
check_chase_error (int dfd,
|
||||
const char *path,
|
||||
GlnxChaseFlags flags,
|
||||
GQuark err_domain,
|
||||
gint err_code)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
glnx_autofd int chase_fd = -1;
|
||||
|
||||
/* let's try to test the openat2 impl */
|
||||
chase_fd = glnx_chaseat (dfd, path, flags, &error);
|
||||
g_assert_cmpint (chase_fd, <, 0);
|
||||
g_assert_error (error, err_domain, err_code);
|
||||
g_clear_error (&error);
|
||||
|
||||
/* let's try to test the open_tree impl */
|
||||
chase_fd = glnx_chaseat (dfd, path,
|
||||
flags | GLNX_CHASE_DEBUG_NO_OPENAT2,
|
||||
&error);
|
||||
g_assert_cmpint (chase_fd, <, 0);
|
||||
g_assert_error (error, err_domain, err_code);
|
||||
g_clear_error (&error);
|
||||
|
||||
/* let's try to test the openat impl */
|
||||
chase_fd = glnx_chaseat (dfd, path,
|
||||
flags |
|
||||
GLNX_CHASE_DEBUG_NO_OPENAT2 |
|
||||
GLNX_CHASE_DEBUG_NO_OPEN_TREE,
|
||||
&error);
|
||||
g_assert_cmpint (chase_fd, <, 0);
|
||||
g_assert_error (error, err_domain, err_code);
|
||||
g_clear_error (&error);
|
||||
}
|
||||
|
||||
static void
|
||||
test_chase_relative (void)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
glnx_autofd int dfd = -1;
|
||||
int expected_ino;
|
||||
|
||||
g_assert_true (glnx_shutil_mkdir_p_at_open (AT_FDCWD, "file/baz", 0755,
|
||||
&dfd,
|
||||
NULL, &error));
|
||||
g_assert_no_error (error);
|
||||
g_assert_cmpint (dfd, >=, 0);
|
||||
|
||||
expected_ino = get_ino (dfd);
|
||||
|
||||
for (size_t i = 0; i < G_N_ELEMENTS (test_paths); i++)
|
||||
check_chase (AT_FDCWD, test_paths[i], 0, expected_ino);
|
||||
|
||||
check_chase_error (AT_FDCWD, "nope", 0, G_IO_ERROR, G_IO_ERROR_NOT_FOUND);
|
||||
}
|
||||
|
||||
static void
|
||||
test_chase_relative_fd (void)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
glnx_autofd int dfd = -1;
|
||||
int expected_ino;
|
||||
glnx_autofd int cwdfd = -1;
|
||||
|
||||
g_assert_true (glnx_shutil_mkdir_p_at_open (AT_FDCWD, "file/baz", 0755,
|
||||
&dfd,
|
||||
NULL, &error));
|
||||
g_assert_no_error (error);
|
||||
g_assert_cmpint (dfd, >=, 0);
|
||||
|
||||
expected_ino = get_ino (dfd);
|
||||
|
||||
cwdfd = openat (AT_FDCWD, ".", O_PATH | O_CLOEXEC | O_NOFOLLOW);
|
||||
g_assert_cmpint (cwdfd, >=, 0);
|
||||
|
||||
for (size_t i = 0; i < G_N_ELEMENTS (test_paths); i++)
|
||||
check_chase (cwdfd, test_paths[i], 0, expected_ino);
|
||||
|
||||
check_chase_error (cwdfd, "nope", 0, G_IO_ERROR, G_IO_ERROR_NOT_FOUND);
|
||||
}
|
||||
|
||||
static void
|
||||
test_chase_absolute (void)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
glnx_autofd int dfd = -1;
|
||||
int expected_ino;
|
||||
glnx_autofd int cwdfd = -1;
|
||||
g_autofree char *proc_fd_path = NULL;
|
||||
g_autofree char *cwd_path = NULL;
|
||||
|
||||
g_assert_true (glnx_shutil_mkdir_p_at_open (AT_FDCWD, "file/baz", 0755,
|
||||
&dfd,
|
||||
NULL, &error));
|
||||
g_assert_no_error (error);
|
||||
g_assert_cmpint (dfd, >=, 0);
|
||||
|
||||
expected_ino = get_ino (dfd);
|
||||
|
||||
cwdfd = openat (AT_FDCWD, ".", O_PATH | O_CLOEXEC | O_NOFOLLOW);
|
||||
g_assert_cmpint (cwdfd, >=, 0);
|
||||
|
||||
cwd_path = get_abspath (cwdfd, "");
|
||||
|
||||
for (size_t i = 0; i < G_N_ELEMENTS (test_paths); i++)
|
||||
{
|
||||
g_autofree char *abspath = NULL;
|
||||
|
||||
abspath = g_strdup_printf ("%s/%s", cwd_path, test_paths[i]);
|
||||
check_chase (AT_FDCWD, abspath, 0, expected_ino);
|
||||
}
|
||||
|
||||
check_chase_error (AT_FDCWD, "/nope/nope/nope/345298308497623012313243543", 0,
|
||||
G_IO_ERROR, G_IO_ERROR_NOT_FOUND);
|
||||
}
|
||||
|
||||
static void
|
||||
test_chase_link (void)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
glnx_autofd int dfd = -1;
|
||||
int link_ino;
|
||||
int target_ino;
|
||||
|
||||
g_assert_true (glnx_shutil_mkdir_p_at_open (AT_FDCWD, "file/baz", 0755,
|
||||
&dfd,
|
||||
NULL, &error));
|
||||
g_assert_no_error (error);
|
||||
g_assert_cmpint (dfd, >=, 0);
|
||||
|
||||
g_assert_cmpint (symlinkat ("file/baz", AT_FDCWD, "link"), ==, 0);
|
||||
|
||||
target_ino = get_ino (dfd);
|
||||
link_ino = path_get_ino ("link");
|
||||
|
||||
check_chase (AT_FDCWD, "link", 0, target_ino);
|
||||
check_chase (AT_FDCWD, "link/", 0, target_ino);
|
||||
check_chase (AT_FDCWD, "link///", 0, target_ino);
|
||||
check_chase (AT_FDCWD, "link/.//.", 0, target_ino);
|
||||
check_chase (AT_FDCWD, "link", 0, target_ino);
|
||||
|
||||
check_chase (AT_FDCWD, "link", GLNX_CHASE_NOFOLLOW, link_ino);
|
||||
check_chase (AT_FDCWD, "./file/../link", GLNX_CHASE_NOFOLLOW, link_ino);
|
||||
check_chase (AT_FDCWD, "link/", GLNX_CHASE_NOFOLLOW, target_ino);
|
||||
check_chase (AT_FDCWD, "././link/.", GLNX_CHASE_NOFOLLOW, target_ino);
|
||||
check_chase (AT_FDCWD, "link/.//", GLNX_CHASE_NOFOLLOW, target_ino);
|
||||
|
||||
check_chase (AT_FDCWD, "link",
|
||||
GLNX_CHASE_NOFOLLOW | GLNX_CHASE_RESOLVE_NO_SYMLINKS,
|
||||
link_ino);
|
||||
check_chase_error (AT_FDCWD, "link",
|
||||
GLNX_CHASE_RESOLVE_NO_SYMLINKS,
|
||||
G_IO_ERROR, G_IO_ERROR_TOO_MANY_LINKS);
|
||||
}
|
||||
|
||||
static void
|
||||
test_chase_resolve (void)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
glnx_autofd int foo_dfd = -1;
|
||||
glnx_autofd int bar_dfd = -1;
|
||||
g_autofree char *foo_abspath = NULL;
|
||||
int ino;
|
||||
|
||||
g_assert_true (glnx_shutil_mkdir_p_at_open (AT_FDCWD, "foo", 0755,
|
||||
&foo_dfd,
|
||||
NULL, &error));
|
||||
g_assert_no_error (error);
|
||||
g_assert_cmpint (foo_dfd, >=, 0);
|
||||
|
||||
g_assert_true (glnx_shutil_mkdir_p_at_open (AT_FDCWD, "foo/bar", 0755,
|
||||
&bar_dfd,
|
||||
NULL, &error));
|
||||
g_assert_no_error (error);
|
||||
g_assert_cmpint (bar_dfd, >=, 0);
|
||||
|
||||
foo_abspath = get_abspath (foo_dfd, "");
|
||||
|
||||
g_assert_cmpint (symlinkat ("..", foo_dfd, "link1"), ==, 0);
|
||||
g_assert_cmpint (symlinkat ("bar/../..", foo_dfd, "link2"), ==, 0);
|
||||
g_assert_cmpint (symlinkat (foo_abspath, foo_dfd, "link3"), ==, 0);
|
||||
g_assert_cmpint (symlinkat ("/bar", foo_dfd, "link4"), ==, 0);
|
||||
g_assert_cmpint (symlinkat ("link1/foo", foo_dfd, "link5"), ==, 0);
|
||||
g_assert_cmpint (symlinkat ("link7", foo_dfd, "link6"), ==, 0);
|
||||
g_assert_cmpint (symlinkat ("link6", foo_dfd, "link7"), ==, 0);
|
||||
|
||||
ino = get_ino (bar_dfd);
|
||||
|
||||
/* A bunch of different ways to get from CWD and foo to bar */
|
||||
check_chase (foo_dfd, "./bar", 0, ino);
|
||||
check_chase (foo_dfd, "../foo/bar", 0, ino);
|
||||
check_chase (foo_dfd, "link1/foo/bar", 0, ino);
|
||||
check_chase (AT_FDCWD, "foo/link1/foo/bar", 0, ino);
|
||||
check_chase (foo_dfd, "link2/foo/bar", 0, ino);
|
||||
check_chase (AT_FDCWD, ".///foo/./link2/foo/bar", 0, ino);
|
||||
check_chase (foo_dfd, "link3/bar", 0, ino);
|
||||
check_chase (AT_FDCWD, ".///foo/./link3/bar", 0, ino);
|
||||
check_chase (foo_dfd, "link5/bar", 0, ino);
|
||||
|
||||
/* check that NO_SYMLINKS works with a component in the middle */
|
||||
check_chase_error (AT_FDCWD, "foo/link3/bar",
|
||||
GLNX_CHASE_RESOLVE_NO_SYMLINKS,
|
||||
G_IO_ERROR, G_IO_ERROR_TOO_MANY_LINKS);
|
||||
|
||||
/* link6 points to link 7, points to link6, ... This should error out! */
|
||||
check_chase_error (foo_dfd, "link6/bar", 0,
|
||||
G_IO_ERROR, G_IO_ERROR_TOO_MANY_LINKS);
|
||||
|
||||
/* Test with links which never go below the dfd */
|
||||
check_chase (AT_FDCWD, "foo/link1/foo/bar",
|
||||
GLNX_CHASE_RESOLVE_BENEATH,
|
||||
ino);
|
||||
check_chase (AT_FDCWD, "foo/link2/foo/bar",
|
||||
GLNX_CHASE_RESOLVE_BENEATH,
|
||||
ino);
|
||||
/* An absolute link is always below the dfd */
|
||||
check_chase_error (AT_FDCWD, "foo/link3/foo/bar",
|
||||
GLNX_CHASE_RESOLVE_BENEATH,
|
||||
G_IO_ERROR, G_IO_ERROR_FAILED);
|
||||
|
||||
/* Same, but from foo instead of cwd */
|
||||
check_chase_error (foo_dfd, "link1/foo/bar",
|
||||
GLNX_CHASE_RESOLVE_BENEATH,
|
||||
G_IO_ERROR, G_IO_ERROR_FAILED);
|
||||
check_chase_error (foo_dfd, "link2/foo/bar",
|
||||
GLNX_CHASE_RESOLVE_BENEATH,
|
||||
G_IO_ERROR, G_IO_ERROR_FAILED);
|
||||
check_chase_error (foo_dfd, "link3/foo/bar",
|
||||
GLNX_CHASE_RESOLVE_BENEATH,
|
||||
G_IO_ERROR, G_IO_ERROR_FAILED);
|
||||
|
||||
/* Check that trying to be below the dfd with RESOLVE_IN_ROOT resolves to the
|
||||
* dfd itself */
|
||||
check_chase (foo_dfd, "link1/bar",
|
||||
GLNX_CHASE_RESOLVE_IN_ROOT,
|
||||
ino);
|
||||
check_chase (foo_dfd, "link2/bar",
|
||||
GLNX_CHASE_RESOLVE_IN_ROOT,
|
||||
ino);
|
||||
/* The absolute link is relative to dfd with RESOLVE_IN_ROOT, so this
|
||||
* fails... */
|
||||
check_chase_error (foo_dfd, "link3",
|
||||
GLNX_CHASE_RESOLVE_IN_ROOT,
|
||||
G_IO_ERROR, G_IO_ERROR_NOT_FOUND);
|
||||
/* ... but the link /bar resolves correctly from foo as dfd. */
|
||||
check_chase (foo_dfd, "link4",
|
||||
GLNX_CHASE_RESOLVE_IN_ROOT,
|
||||
ino);
|
||||
}
|
||||
|
||||
static void
|
||||
test_chase_resolve_in_root_absolute (void)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
glnx_autofd int foo_dfd = -1;
|
||||
glnx_autofd int bar_dfd = -1;
|
||||
glnx_autofd int baz_dfd = -1;
|
||||
|
||||
g_assert_true (glnx_shutil_mkdir_p_at_open (AT_FDCWD, "foo", 0755,
|
||||
&foo_dfd,
|
||||
NULL, &error));
|
||||
g_assert_no_error (error);
|
||||
g_assert_cmpint (foo_dfd, >=, 0);
|
||||
|
||||
g_assert_true (glnx_shutil_mkdir_p_at_open (AT_FDCWD, "foo/bar", 0755,
|
||||
&bar_dfd,
|
||||
NULL, &error));
|
||||
g_assert_no_error (error);
|
||||
g_assert_cmpint (bar_dfd, >=, 0);
|
||||
|
||||
g_assert_true (glnx_shutil_mkdir_p_at_open (AT_FDCWD, "foo/bar/baz", 0755,
|
||||
&baz_dfd,
|
||||
NULL, &error));
|
||||
g_assert_no_error (error);
|
||||
g_assert_cmpint (baz_dfd, >=, 0);
|
||||
|
||||
/* Test the absolute symlink doesn't break tracking of the root level */
|
||||
g_assert_cmpint (symlinkat ("/..", baz_dfd, "link1"), ==, 0);
|
||||
|
||||
/* We should not be able to break out of the root! */
|
||||
check_chase (bar_dfd, "./baz/link1", GLNX_CHASE_RESOLVE_IN_ROOT, get_ino (bar_dfd));
|
||||
}
|
||||
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
_GLNX_TEST_SCOPED_TEMP_DIR;
|
||||
int ret;
|
||||
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
|
||||
g_test_add_func ("/chase-relative", test_chase_relative);
|
||||
g_test_add_func ("/chase-relative-fd", test_chase_relative_fd);
|
||||
g_test_add_func ("/chase-absolute", test_chase_absolute);
|
||||
g_test_add_func ("/chase-link", test_chase_link);
|
||||
g_test_add_func ("/chase-resolve", test_chase_resolve);
|
||||
g_test_add_func ("/chase-resolve-in-root-absolute", test_chase_resolve_in_root_absolute);
|
||||
|
||||
ret = g_test_run();
|
||||
|
||||
return ret;
|
||||
}
|
||||
Reference in New Issue
Block a user