github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/cmd/libsnap-confine-private/locking-test.c (about) 1 /* 2 * Copyright (C) 2017 Canonical Ltd 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License version 3 as 6 * published by the Free Software Foundation. 7 * 8 * This program is distributed in the hope that it will be useful, 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * GNU General Public License for more details. 12 * 13 * You should have received a copy of the GNU General Public License 14 * along with this program. If not, see <http://www.gnu.org/licenses/>. 15 * 16 */ 17 18 #include "locking.h" 19 #include "locking.c" 20 21 #include "../libsnap-confine-private/cleanup-funcs.h" 22 #include "../libsnap-confine-private/test-utils.h" 23 24 #include <errno.h> 25 26 #include <glib.h> 27 #include <glib/gstdio.h> 28 29 // Set alternate locking directory 30 static void sc_set_lock_dir(const char *dir) 31 { 32 sc_lock_dir = dir; 33 } 34 35 // A variant of unsetenv that is compatible with GDestroyNotify 36 static void my_unsetenv(const char *k) 37 { 38 unsetenv(k); 39 } 40 41 // Use temporary directory for locking. 42 // 43 // The directory is automatically reset to the real value at the end of the 44 // test. 45 static const char *sc_test_use_fake_lock_dir(void) 46 { 47 char *lock_dir = NULL; 48 if (g_test_subprocess()) { 49 // Check if the environment variable is set. If so then someone is already 50 // managing the temporary directory and we should not create a new one. 51 lock_dir = getenv("SNAP_CONFINE_LOCK_DIR"); 52 g_assert_nonnull(lock_dir); 53 } else { 54 lock_dir = g_dir_make_tmp(NULL, NULL); 55 g_assert_nonnull(lock_dir); 56 g_test_queue_free(lock_dir); 57 g_assert_cmpint(setenv("SNAP_CONFINE_LOCK_DIR", lock_dir, 0), 58 ==, 0); 59 g_test_queue_destroy((GDestroyNotify) my_unsetenv, 60 "SNAP_CONFINE_LOCK_DIR"); 61 g_test_queue_destroy((GDestroyNotify) rm_rf_tmp, lock_dir); 62 } 63 g_test_queue_destroy((GDestroyNotify) sc_set_lock_dir, SC_LOCK_DIR); 64 sc_set_lock_dir(lock_dir); 65 return lock_dir; 66 } 67 68 // Check that locking a namespace actually flock's the mutex with LOCK_EX 69 static void test_sc_lock_unlock(void) 70 { 71 if (geteuid() != 0) { 72 g_test_skip("this test only runs as root"); 73 return; 74 } 75 76 const char *lock_dir = sc_test_use_fake_lock_dir(); 77 int fd = sc_lock_generic("foo", 123); 78 // Construct the name of the lock file 79 char *lock_file SC_CLEANUP(sc_cleanup_string) = NULL; 80 lock_file = g_strdup_printf("%s/foo.123.lock", lock_dir); 81 // Open the lock file again to obtain a separate file descriptor. 82 // According to flock(2) locks are associated with an open file table entry 83 // so this descriptor will be separate and can compete for the same lock. 84 int lock_fd SC_CLEANUP(sc_cleanup_close) = -1; 85 lock_fd = open(lock_file, O_RDWR | O_CLOEXEC | O_NOFOLLOW); 86 g_assert_cmpint(lock_fd, !=, -1); 87 // The non-blocking lock operation should fail with EWOULDBLOCK as the lock 88 // file is locked by sc_nlock_ns_mutex() already. 89 int err = flock(lock_fd, LOCK_EX | LOCK_NB); 90 int saved_errno = errno; 91 g_assert_cmpint(err, ==, -1); 92 g_assert_cmpint(saved_errno, ==, EWOULDBLOCK); 93 // Unlock the lock. 94 sc_unlock(fd); 95 // Re-attempt the locking operation. This time it should succeed. 96 err = flock(lock_fd, LOCK_EX | LOCK_NB); 97 g_assert_cmpint(err, ==, 0); 98 } 99 100 // Check that holding a lock is properly detected. 101 static void test_sc_verify_snap_lock__locked(void) 102 { 103 if (geteuid() != 0) { 104 g_test_skip("this test only runs as root"); 105 return; 106 } 107 108 (void)sc_test_use_fake_lock_dir(); 109 int fd = sc_lock_snap("foo"); 110 sc_verify_snap_lock("foo"); 111 sc_unlock(fd); 112 } 113 114 // Check that holding a lock is properly detected. 115 static void test_sc_verify_snap_lock__unlocked(void) 116 { 117 if (geteuid() != 0) { 118 g_test_skip("this test only runs as root"); 119 return; 120 } 121 122 (void)sc_test_use_fake_lock_dir(); 123 if (g_test_subprocess()) { 124 sc_verify_snap_lock("foo"); 125 return; 126 } 127 g_test_trap_subprocess(NULL, 0, 0); 128 g_test_trap_assert_failed(); 129 g_test_trap_assert_stderr 130 ("unexpectedly managed to acquire exclusive lock over snap foo\n"); 131 } 132 133 static void test_sc_enable_sanity_timeout(void) 134 { 135 if (geteuid() != 0) { 136 g_test_skip("this test only runs as root"); 137 return; 138 } 139 140 if (g_test_subprocess()) { 141 sc_enable_sanity_timeout(); 142 debug("waiting..."); 143 usleep(7 * G_USEC_PER_SEC); 144 debug("woke up"); 145 sc_disable_sanity_timeout(); 146 return; 147 } 148 g_test_trap_subprocess(NULL, 1 * G_USEC_PER_SEC, 149 G_TEST_SUBPROCESS_INHERIT_STDERR); 150 g_test_trap_assert_failed(); 151 g_test_trap_assert_stderr 152 ("sanity timeout expired: Interrupted system call\n"); 153 } 154 155 static void __attribute__((constructor)) init(void) 156 { 157 g_test_add_func("/locking/sc_lock_unlock", test_sc_lock_unlock); 158 g_test_add_func("/locking/sc_enable_sanity_timeout", 159 test_sc_enable_sanity_timeout); 160 g_test_add_func("/locking/sc_verify_snap_lock__locked", 161 test_sc_verify_snap_lock__locked); 162 g_test_add_func("/locking/sc_verify_snap_lock__unlocked", 163 test_sc_verify_snap_lock__unlocked); 164 }