|
48 | 48 | #undef pr_fmt
|
49 | 49 | #define pr_fmt(fmt) "Kernel/User page tables isolation: " fmt
|
50 | 50 |
|
| 51 | +/* Backporting helper */ |
| 52 | +#ifndef __GFP_NOTRACK |
| 53 | +#define __GFP_NOTRACK 0 |
| 54 | +#endif |
| 55 | + |
51 | 56 | static void __init pti_print_if_insecure(const char *reason)
|
52 | 57 | {
|
53 | 58 | if (boot_cpu_has_bug(X86_BUG_CPU_INSECURE))
|
@@ -137,6 +142,128 @@ pgd_t __pti_set_user_pgd(pgd_t *pgdp, pgd_t pgd)
|
137 | 142 | return pgd;
|
138 | 143 | }
|
139 | 144 |
|
| 145 | +/* |
| 146 | + * Walk the user copy of the page tables (optionally) trying to allocate |
| 147 | + * page table pages on the way down. |
| 148 | + * |
| 149 | + * Returns a pointer to a P4D on success, or NULL on failure. |
| 150 | + */ |
| 151 | +static p4d_t *pti_user_pagetable_walk_p4d(unsigned long address) |
| 152 | +{ |
| 153 | + pgd_t *pgd = kernel_to_user_pgdp(pgd_offset_k(address)); |
| 154 | + gfp_t gfp = (GFP_KERNEL | __GFP_NOTRACK | __GFP_ZERO); |
| 155 | + |
| 156 | + if (address < PAGE_OFFSET) { |
| 157 | + WARN_ONCE(1, "attempt to walk user address\n"); |
| 158 | + return NULL; |
| 159 | + } |
| 160 | + |
| 161 | + if (pgd_none(*pgd)) { |
| 162 | + unsigned long new_p4d_page = __get_free_page(gfp); |
| 163 | + if (!new_p4d_page) |
| 164 | + return NULL; |
| 165 | + |
| 166 | + if (pgd_none(*pgd)) { |
| 167 | + set_pgd(pgd, __pgd(_KERNPG_TABLE | __pa(new_p4d_page))); |
| 168 | + new_p4d_page = 0; |
| 169 | + } |
| 170 | + if (new_p4d_page) |
| 171 | + free_page(new_p4d_page); |
| 172 | + } |
| 173 | + BUILD_BUG_ON(pgd_large(*pgd) != 0); |
| 174 | + |
| 175 | + return p4d_offset(pgd, address); |
| 176 | +} |
| 177 | + |
| 178 | +/* |
| 179 | + * Walk the user copy of the page tables (optionally) trying to allocate |
| 180 | + * page table pages on the way down. |
| 181 | + * |
| 182 | + * Returns a pointer to a PMD on success, or NULL on failure. |
| 183 | + */ |
| 184 | +static pmd_t *pti_user_pagetable_walk_pmd(unsigned long address) |
| 185 | +{ |
| 186 | + gfp_t gfp = (GFP_KERNEL | __GFP_NOTRACK | __GFP_ZERO); |
| 187 | + p4d_t *p4d = pti_user_pagetable_walk_p4d(address); |
| 188 | + pud_t *pud; |
| 189 | + |
| 190 | + BUILD_BUG_ON(p4d_large(*p4d) != 0); |
| 191 | + if (p4d_none(*p4d)) { |
| 192 | + unsigned long new_pud_page = __get_free_page(gfp); |
| 193 | + if (!new_pud_page) |
| 194 | + return NULL; |
| 195 | + |
| 196 | + if (p4d_none(*p4d)) { |
| 197 | + set_p4d(p4d, __p4d(_KERNPG_TABLE | __pa(new_pud_page))); |
| 198 | + new_pud_page = 0; |
| 199 | + } |
| 200 | + if (new_pud_page) |
| 201 | + free_page(new_pud_page); |
| 202 | + } |
| 203 | + |
| 204 | + pud = pud_offset(p4d, address); |
| 205 | + /* The user page tables do not use large mappings: */ |
| 206 | + if (pud_large(*pud)) { |
| 207 | + WARN_ON(1); |
| 208 | + return NULL; |
| 209 | + } |
| 210 | + if (pud_none(*pud)) { |
| 211 | + unsigned long new_pmd_page = __get_free_page(gfp); |
| 212 | + if (!new_pmd_page) |
| 213 | + return NULL; |
| 214 | + |
| 215 | + if (pud_none(*pud)) { |
| 216 | + set_pud(pud, __pud(_KERNPG_TABLE | __pa(new_pmd_page))); |
| 217 | + new_pmd_page = 0; |
| 218 | + } |
| 219 | + if (new_pmd_page) |
| 220 | + free_page(new_pmd_page); |
| 221 | + } |
| 222 | + |
| 223 | + return pmd_offset(pud, address); |
| 224 | +} |
| 225 | + |
| 226 | +static void __init |
| 227 | +pti_clone_pmds(unsigned long start, unsigned long end, pmdval_t clear) |
| 228 | +{ |
| 229 | + unsigned long addr; |
| 230 | + |
| 231 | + /* |
| 232 | + * Clone the populated PMDs which cover start to end. These PMD areas |
| 233 | + * can have holes. |
| 234 | + */ |
| 235 | + for (addr = start; addr < end; addr += PMD_SIZE) { |
| 236 | + pmd_t *pmd, *target_pmd; |
| 237 | + pgd_t *pgd; |
| 238 | + p4d_t *p4d; |
| 239 | + pud_t *pud; |
| 240 | + |
| 241 | + pgd = pgd_offset_k(addr); |
| 242 | + if (WARN_ON(pgd_none(*pgd))) |
| 243 | + return; |
| 244 | + p4d = p4d_offset(pgd, addr); |
| 245 | + if (WARN_ON(p4d_none(*p4d))) |
| 246 | + return; |
| 247 | + pud = pud_offset(p4d, addr); |
| 248 | + if (pud_none(*pud)) |
| 249 | + continue; |
| 250 | + pmd = pmd_offset(pud, addr); |
| 251 | + if (pmd_none(*pmd)) |
| 252 | + continue; |
| 253 | + |
| 254 | + target_pmd = pti_user_pagetable_walk_pmd(addr); |
| 255 | + if (WARN_ON(!target_pmd)) |
| 256 | + return; |
| 257 | + |
| 258 | + /* |
| 259 | + * Copy the PMD. That is, the kernelmode and usermode |
| 260 | + * tables will share the last-level page tables of this |
| 261 | + * address range |
| 262 | + */ |
| 263 | + *target_pmd = pmd_clear_flags(*pmd, clear); |
| 264 | + } |
| 265 | +} |
| 266 | + |
140 | 267 | /*
|
141 | 268 | * Initialize kernel page table isolation
|
142 | 269 | */
|
|
0 commit comments