1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
|
// SPDX-License-Identifier: GPL-2.0-only
/*
* Landlock - Domain management
*
* Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
* Copyright © 2018-2020 ANSSI
* Copyright © 2024-2025 Microsoft Corporation
*/
#include <kunit/test.h>
#include <linux/bitops.h>
#include <linux/bits.h>
#include <linux/cred.h>
#include <linux/file.h>
#include <linux/mm.h>
#include <linux/path.h>
#include <linux/pid.h>
#include <linux/sched.h>
#include <linux/signal.h>
#include <linux/uidgid.h>
#include "access.h"
#include "common.h"
#include "domain.h"
#include "id.h"
#ifdef CONFIG_AUDIT
/**
* get_current_exe - Get the current's executable path, if any
*
* @exe_str: Returned pointer to a path string with a lifetime tied to the
* returned buffer, if any.
* @exe_size: Returned size of @exe_str (including the trailing null
* character), if any.
*
* Returns: A pointer to an allocated buffer where @exe_str point to, %NULL if
* there is no executable path, or an error otherwise.
*/
static const void *get_current_exe(const char **const exe_str,
size_t *const exe_size)
{
const size_t buffer_size = LANDLOCK_PATH_MAX_SIZE;
struct mm_struct *mm = current->mm;
struct file *file __free(fput) = NULL;
char *buffer __free(kfree) = NULL;
const char *exe;
ssize_t size;
if (!mm)
return NULL;
file = get_mm_exe_file(mm);
if (!file)
return NULL;
buffer = kmalloc(buffer_size, GFP_KERNEL);
if (!buffer)
return ERR_PTR(-ENOMEM);
exe = d_path(&file->f_path, buffer, buffer_size);
if (WARN_ON_ONCE(IS_ERR(exe)))
/* Should never happen according to LANDLOCK_PATH_MAX_SIZE. */
return ERR_CAST(exe);
size = buffer + buffer_size - exe;
if (WARN_ON_ONCE(size <= 0))
return ERR_PTR(-ENAMETOOLONG);
*exe_size = size;
*exe_str = exe;
return no_free_ptr(buffer);
}
/*
* Returns: A newly allocated object describing a domain, or an error
* otherwise.
*/
static struct landlock_details *get_current_details(void)
{
/* Cf. audit_log_d_path_exe() */
static const char null_path[] = "(null)";
const char *path_str = null_path;
size_t path_size = sizeof(null_path);
const void *buffer __free(kfree) = NULL;
struct landlock_details *details;
buffer = get_current_exe(&path_str, &path_size);
if (IS_ERR(buffer))
return ERR_CAST(buffer);
/*
* Create the new details according to the path's length. Do not
* allocate with GFP_KERNEL_ACCOUNT because it is independent from the
* caller.
*/
details =
kzalloc(struct_size(details, exe_path, path_size), GFP_KERNEL);
if (!details)
return ERR_PTR(-ENOMEM);
memcpy(details->exe_path, path_str, path_size);
details->pid = get_pid(task_tgid(current));
details->uid = from_kuid(&init_user_ns, current_uid());
get_task_comm(details->comm, current);
return details;
}
/**
* landlock_init_hierarchy_log - Partially initialize landlock_hierarchy
*
* @hierarchy: The hierarchy to initialize.
*
* The current task is referenced as the domain that is enforcing the
* restriction. The subjective credentials must not be in an overridden state.
*
* @hierarchy->parent and @hierarchy->usage should already be set.
*/
int landlock_init_hierarchy_log(struct landlock_hierarchy *const hierarchy)
{
struct landlock_details *details;
details = get_current_details();
if (IS_ERR(details))
return PTR_ERR(details);
hierarchy->details = details;
hierarchy->id = landlock_get_id_range(1);
hierarchy->log_status = LANDLOCK_LOG_PENDING;
hierarchy->log_same_exec = true;
hierarchy->log_new_exec = false;
atomic64_set(&hierarchy->num_denials, 0);
return 0;
}
static deny_masks_t
get_layer_deny_mask(const access_mask_t all_existing_optional_access,
const unsigned long access_bit, const size_t layer)
{
unsigned long access_weight;
/* This may require change with new object types. */
WARN_ON_ONCE(all_existing_optional_access !=
_LANDLOCK_ACCESS_FS_OPTIONAL);
if (WARN_ON_ONCE(layer >= LANDLOCK_MAX_NUM_LAYERS))
return 0;
access_weight = hweight_long(all_existing_optional_access &
GENMASK(access_bit, 0));
if (WARN_ON_ONCE(access_weight < 1))
return 0;
return layer
<< ((access_weight - 1) * HWEIGHT(LANDLOCK_MAX_NUM_LAYERS - 1));
}
#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
static void test_get_layer_deny_mask(struct kunit *const test)
{
const unsigned long truncate = BIT_INDEX(LANDLOCK_ACCESS_FS_TRUNCATE);
const unsigned long ioctl_dev = BIT_INDEX(LANDLOCK_ACCESS_FS_IOCTL_DEV);
KUNIT_EXPECT_EQ(test, 0,
get_layer_deny_mask(_LANDLOCK_ACCESS_FS_OPTIONAL,
truncate, 0));
KUNIT_EXPECT_EQ(test, 0x3,
get_layer_deny_mask(_LANDLOCK_ACCESS_FS_OPTIONAL,
truncate, 3));
KUNIT_EXPECT_EQ(test, 0,
get_layer_deny_mask(_LANDLOCK_ACCESS_FS_OPTIONAL,
ioctl_dev, 0));
KUNIT_EXPECT_EQ(test, 0xf0,
get_layer_deny_mask(_LANDLOCK_ACCESS_FS_OPTIONAL,
ioctl_dev, 15));
}
#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
deny_masks_t
landlock_get_deny_masks(const access_mask_t all_existing_optional_access,
const access_mask_t optional_access,
const layer_mask_t (*const layer_masks)[],
const size_t layer_masks_size)
{
const unsigned long access_opt = optional_access;
unsigned long access_bit;
deny_masks_t deny_masks = 0;
/* This may require change with new object types. */
WARN_ON_ONCE(access_opt !=
(optional_access & all_existing_optional_access));
if (WARN_ON_ONCE(!layer_masks))
return 0;
if (WARN_ON_ONCE(!access_opt))
return 0;
for_each_set_bit(access_bit, &access_opt, layer_masks_size) {
const layer_mask_t mask = (*layer_masks)[access_bit];
if (!mask)
continue;
/* __fls(1) == 0 */
deny_masks |= get_layer_deny_mask(all_existing_optional_access,
access_bit, __fls(mask));
}
return deny_masks;
}
#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
static void test_landlock_get_deny_masks(struct kunit *const test)
{
const layer_mask_t layers1[BITS_PER_TYPE(access_mask_t)] = {
[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT_ULL(0) |
BIT_ULL(9),
[BIT_INDEX(LANDLOCK_ACCESS_FS_TRUNCATE)] = BIT_ULL(1),
[BIT_INDEX(LANDLOCK_ACCESS_FS_IOCTL_DEV)] = BIT_ULL(2) |
BIT_ULL(0),
};
KUNIT_EXPECT_EQ(test, 0x1,
landlock_get_deny_masks(_LANDLOCK_ACCESS_FS_OPTIONAL,
LANDLOCK_ACCESS_FS_TRUNCATE,
&layers1, ARRAY_SIZE(layers1)));
KUNIT_EXPECT_EQ(test, 0x20,
landlock_get_deny_masks(_LANDLOCK_ACCESS_FS_OPTIONAL,
LANDLOCK_ACCESS_FS_IOCTL_DEV,
&layers1, ARRAY_SIZE(layers1)));
KUNIT_EXPECT_EQ(
test, 0x21,
landlock_get_deny_masks(_LANDLOCK_ACCESS_FS_OPTIONAL,
LANDLOCK_ACCESS_FS_TRUNCATE |
LANDLOCK_ACCESS_FS_IOCTL_DEV,
&layers1, ARRAY_SIZE(layers1)));
}
#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
static struct kunit_case test_cases[] = {
/* clang-format off */
KUNIT_CASE(test_get_layer_deny_mask),
KUNIT_CASE(test_landlock_get_deny_masks),
{}
/* clang-format on */
};
static struct kunit_suite test_suite = {
.name = "landlock_domain",
.test_cases = test_cases,
};
kunit_test_suite(test_suite);
#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
#endif /* CONFIG_AUDIT */
|