summaryrefslogtreecommitdiff
path: root/mm/mseal.c
blob: 7a8ac66dc215f3f748f9dded2cdfcaf52da0606e (plain)
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
// SPDX-License-Identifier: GPL-2.0
/*
 *  Implement mseal() syscall.
 *
 *  Copyright (c) 2023,2024 Google, Inc.
 *
 *  Author: Jeff Xu <jeffxu@chromium.org>
 */

#include <linux/mempolicy.h>
#include <linux/minmax.h>
#include <linux/mman.h>
#include <linux/mm.h>
#include <linux/mm_inline.h>
#include <linux/syscalls.h>
#include <linux/sched.h>
#include "internal.h"

static bool range_contains_unmapped(unsigned long start, unsigned long end)
{
	VMA_ITERATOR(vmi, current->mm, start);
	unsigned long prev_end = start;
	struct vm_area_struct *vma;

	for_each_vma_range(vmi, vma, end) {
		if (vma->vm_start > prev_end)
			return true;

		prev_end = vma->vm_end;
	}

	return prev_end < end;
}

static int __mseal_range(unsigned long start, unsigned long end)
{
	VMA_ITERATOR(vmi, current->mm, start);
	struct vm_area_struct *vma, *prev;

	/* We know there are no gaps so this will be non-NULL. */
	vma = vma_iter_load(&vmi);
	prev = vma_prev(&vmi);
	if (start > vma->vm_start)
		prev = vma;

	for_each_vma_range(vmi, vma, end) {
		const unsigned long curr_start = max(vma->vm_start, start);
		const unsigned long curr_end = min(vma->vm_end, end);

		if (!vma_test(vma, VMA_SEALED_BIT)) {
			vma_flags_t vma_flags = vma->flags;

			vma_flags_set(&vma_flags, VMA_SEALED_BIT);

			vma = vma_modify_flags(&vmi, prev, vma, curr_start,
					       curr_end, &vma_flags);
			if (IS_ERR(vma))
				return PTR_ERR(vma);
			vma_start_write(vma);
			vma_set_flags(vma, VMA_SEALED_BIT);
		}

		prev = vma;
	}

	return 0;
}

static int mseal_range(unsigned long start, unsigned long end)
{
	int err;

	err = mmap_write_lock_killable(current->mm);
	if (err)
		return err;
	if (range_contains_unmapped(start, end))
		err = -ENOMEM;
	else
		err = __mseal_range(start, end);
	mmap_write_unlock(current->mm);
	return err;
}

/**
 * mseal_mmap_page_zero() - If the MMAP_PAGE_ZERO personality is set, mseal()
 * the page mapped at address zero.
 */
void mseal_mmap_page_zero(void)
{
	int err;

	if (WARN_ON_ONCE(!(current->personality & MMAP_PAGE_ZERO)))
		return;

	err = mseal_range(0, PAGE_SIZE);
	if (err)
		pr_warn_ratelimited("pid=%d, couldn't seal address 0, ret=%d.\n",
				    task_pid_nr(current), err);
}

/*
 * Seal VMAs in the specified input range to prevent an attacker replacing what
 * is mapped in the range with something else.
 *
 * Disallows:
 * - VMA unmapping, remapping or shrinking.
 * - Overwriting the VMA with another one via mmap(), mremap() or similar.
 * - Alteration of properties via mprotect()/pkey_mprotect().
 * - Destructive madvise() behaviours (like MADV_DONTNEED) on anonymous read-only
 *   ranges.
 *
 * Since unmapped ranges can be mapped at any time, the input range must span
 * mapped ranges only.
 *
 * The flags parameter is currently reserved.
 */
SYSCALL_DEFINE3(mseal, unsigned long, start, size_t, len, unsigned long, flags)
{
	size_t len_aligned;
	unsigned long end;

	/* Verify flags not set. */
	if (flags)
		return -EINVAL;

	start = untagged_addr(start);
	if (!PAGE_ALIGNED(start))
		return -EINVAL;

	len_aligned = PAGE_ALIGN(len);
	/* Check to see whether len was rounded up from small -ve to zero. */
	if (len && !len_aligned)
		return -EINVAL;

	end = start + len_aligned;
	if (end < start)
		return -EINVAL;

	if (end == start)
		return 0;

	return mseal_range(start, end);
}