hypervisor kernel integrity enforcement via EPT/NPT write interception for Xen/HVM guests
14

Configure Feed

Select the types of activity you want to include in your feed.

turnstile / xen-patch / turnstile.c
15 kB 617 lines
1#include <xen/sched.h> 2#include <xen/domain.h> 3#include <xen/guest_access.h> 4#include <xen/hypercall.h> 5#include <xen/mm.h> 6#include <xen/errno.h> 7#include <xen/lib.h> 8#include <asm/p2m.h> 9#include <asm/turnstile.h> 10 11static struct turnstile_domain_state *get_turnstile_state(struct domain *d) 12{ 13 return d->arch.turnstile; 14} 15 16int turnstile_domain_init(struct domain *d) 17{ 18 struct turnstile_domain_state *state; 19 20 state = xzalloc(struct turnstile_domain_state); 21 if ( !state ) 22 return -ENOMEM; 23 24 rwlock_init(&state->lock); 25 state->policy = TURNSTILE_POLICY_DISABLED; 26 state->ring_head = 0; 27 state->ring_tail = 0; 28 state->ring_seq = 0; 29 state->overflow_count = 0; 30 state->num_ranges = 0; 31 state->func_entries = NULL; 32 state->num_func_entries = 0; 33 state->jump_entries = NULL; 34 state->num_jump_entries = 0; 35 state->write_grant.active = false; 36 37 d->arch.turnstile = state; 38 39 return 0; 40} 41 42void turnstile_domain_destroy(struct domain *d) 43{ 44 struct turnstile_domain_state *state = get_turnstile_state(d); 45 46 if ( !state ) 47 return; 48 49 xfree(state->func_entries); 50 xfree(state->jump_entries); 51 xfree(state); 52 d->arch.turnstile = NULL; 53} 54 55static bool gpa_in_range(struct turnstile_domain_state *state, uint64_t gpa) 56{ 57 unsigned int i; 58 59 for ( i = 0; i < state->num_ranges; i++ ) 60 { 61 if ( state->ranges[i].active && 62 gpa >= state->ranges[i].gpa_start && 63 gpa < state->ranges[i].gpa_end ) 64 return true; 65 } 66 return false; 67} 68 69static bool check_write_grant(struct turnstile_domain_state *state, uint64_t gpa, 70 bool *grant_expired) 71{ 72 *grant_expired = false; 73 74 if ( !state->write_grant.active ) 75 return false; 76 77 if ( NOW() > state->write_grant.expiry ) 78 { 79 *grant_expired = true; 80 return false; 81 } 82 83 return gpa >= state->write_grant.gpa_start && 84 gpa < state->write_grant.gpa_end; 85} 86 87static int check_implicit_ftrace(struct turnstile_domain_state *state, 88 uint64_t gpa, const uint8_t *insn_bytes) 89{ 90 unsigned int i; 91 uint64_t vaddr = gpa + 0xffff800000000000ULL; 92 93 for ( i = 0; i < state->num_func_entries; i++ ) 94 { 95 if ( state->func_entries[i] == vaddr ) 96 { 97 if ( insn_bytes[0] == 0xe8 ) 98 return TURNSTILE_IMPLICIT_FTRACE; 99 100 if ( insn_bytes[0] == 0x0f && insn_bytes[1] == 0x1f && 101 insn_bytes[2] == 0x44 && insn_bytes[3] == 0x00 && 102 insn_bytes[4] == 0x00 ) 103 return TURNSTILE_IMPLICIT_FTRACE; 104 105 if ( insn_bytes[0] == 0x90 || 106 (insn_bytes[0] == 0x66 && insn_bytes[1] == 0x90) ) 107 return TURNSTILE_IMPLICIT_FTRACE; 108 } 109 } 110 return TURNSTILE_IMPLICIT_DENIED; 111} 112 113static int check_implicit_static_key(struct turnstile_domain_state *state, 114 uint64_t gpa, const uint8_t *insn_bytes) 115{ 116 unsigned int i; 117 uint64_t vaddr = gpa + 0xffff800000000000ULL; 118 119 for ( i = 0; i < state->num_jump_entries; i++ ) 120 { 121 uint64_t code_addr = state->jump_entries[i * 3]; 122 if ( code_addr == vaddr ) 123 { 124 if ( insn_bytes[0] == 0x0f && insn_bytes[1] == 0x1f ) 125 return TURNSTILE_IMPLICIT_STATIC; 126 127 if ( insn_bytes[0] == 0xe9 ) 128 return TURNSTILE_IMPLICIT_STATIC; 129 130 if ( insn_bytes[0] == 0xeb ) 131 return TURNSTILE_IMPLICIT_STATIC; 132 133 if ( insn_bytes[0] == 0x90 ) 134 return TURNSTILE_IMPLICIT_STATIC; 135 136 if ( insn_bytes[0] == 0x66 && insn_bytes[1] == 0x90 ) 137 return TURNSTILE_IMPLICIT_STATIC; 138 } 139 } 140 return TURNSTILE_IMPLICIT_DENIED; 141} 142 143static void log_violation(struct turnstile_domain_state *state, 144 uint64_t gpa, uint64_t rip, uint64_t cr3, 145 uint32_t access_flags, uint32_t response, 146 const uint8_t *insn_bytes) 147{ 148 struct turnstile_violation *v; 149 uint32_t head; 150 uint32_t next_head; 151 152 head = state->ring_head; 153 next_head = (head + 1) % TURNSTILE_RING_SIZE; 154 155 if ( next_head == state->ring_tail ) 156 { 157 state->overflow_count++; 158 state->stats.ring_overflows++; 159 state->ring_tail = (state->ring_tail + 1) % TURNSTILE_RING_SIZE; 160 } 161 162 v = &state->ring[head]; 163 v->timestamp = NOW(); 164 v->gpa = gpa; 165 v->rip = rip; 166 v->cr3 = cr3; 167 v->access_flags = access_flags; 168 v->response = response; 169 v->seq = state->ring_seq++; 170 memcpy(v->insn_bytes, insn_bytes, 16); 171 172 state->ring_head = next_head; 173 state->stats.violations_total++; 174 175 if ( response ) 176 state->stats.violations_blocked++; 177 else 178 state->stats.violations_allowed++; 179} 180 181int turnstile_check_violation(struct domain *d, uint64_t gpa, uint64_t rip, 182 uint64_t cr3, uint32_t access_flags, 183 const uint8_t *insn_bytes) 184{ 185 struct turnstile_domain_state *state = get_turnstile_state(d); 186 int implicit; 187 int block = 0; 188 bool in_range; 189 bool grant_valid; 190 bool grant_expired; 191 uint32_t policy; 192 193 if ( !state || state->policy == TURNSTILE_POLICY_DISABLED ) 194 return 0; 195 196 read_lock(&state->lock); 197 198 in_range = gpa_in_range(state, gpa); 199 if ( !in_range ) 200 { 201 read_unlock(&state->lock); 202 return 0; 203 } 204 205 grant_valid = check_write_grant(state, gpa, &grant_expired); 206 if ( grant_valid ) 207 { 208 read_unlock(&state->lock); 209 write_lock(&state->lock); 210 state->stats.explicit_write_grants++; 211 write_unlock(&state->lock); 212 return 0; 213 } 214 215 implicit = check_implicit_ftrace(state, gpa, insn_bytes); 216 if ( implicit == TURNSTILE_IMPLICIT_FTRACE ) 217 { 218 read_unlock(&state->lock); 219 write_lock(&state->lock); 220 state->stats.implicit_ftrace++; 221 write_unlock(&state->lock); 222 return 0; 223 } 224 225 implicit = check_implicit_static_key(state, gpa, insn_bytes); 226 if ( implicit == TURNSTILE_IMPLICIT_STATIC ) 227 { 228 read_unlock(&state->lock); 229 write_lock(&state->lock); 230 state->stats.implicit_static_key++; 231 write_unlock(&state->lock); 232 return 0; 233 } 234 235 policy = state->policy; 236 read_unlock(&state->lock); 237 238 if ( policy == TURNSTILE_POLICY_ENFORCE ) 239 block = 1; 240 241 write_lock(&state->lock); 242 if ( grant_expired ) 243 state->write_grant.active = false; 244 log_violation(state, gpa, rip, cr3, access_flags, block, insn_bytes); 245 write_unlock(&state->lock); 246 247 return block; 248} 249 250static long turnstile_set_policy(struct domain *d, 251 XEN_GUEST_HANDLE_PARAM(void) arg) 252{ 253 struct turnstile_domain_state *state = get_turnstile_state(d); 254 struct turnstile_op_set_policy op; 255 256 if ( !state ) 257 return -EINVAL; 258 259 if ( copy_from_guest(&op, arg, 1) ) 260 return -EFAULT; 261 262 if ( op.policy > TURNSTILE_POLICY_ENFORCE ) 263 return -EINVAL; 264 265 write_lock(&state->lock); 266 state->policy = op.policy; 267 write_unlock(&state->lock); 268 269 return 0; 270} 271 272static long turnstile_protect_range(struct domain *d, 273 XEN_GUEST_HANDLE_PARAM(void) arg) 274{ 275 struct turnstile_domain_state *state = get_turnstile_state(d); 276 struct turnstile_op_protect_range op; 277 unsigned int i; 278 int slot = -1; 279 280 if ( !state ) 281 return -EINVAL; 282 283 if ( copy_from_guest(&op, arg, 1) ) 284 return -EFAULT; 285 286 if ( op.length == 0 ) 287 return -EINVAL; 288 289 write_lock(&state->lock); 290 291 for ( i = 0; i < state->num_ranges; i++ ) 292 { 293 if ( state->ranges[i].active && 294 state->ranges[i].gpa_start == op.gpa_start && 295 state->ranges[i].gpa_end == op.gpa_start + op.length ) 296 { 297 write_unlock(&state->lock); 298 return 0; 299 } 300 } 301 302 for ( i = 0; i < TURNSTILE_MAX_RANGES; i++ ) 303 { 304 if ( !state->ranges[i].active ) 305 { 306 slot = i; 307 break; 308 } 309 } 310 311 if ( slot < 0 ) 312 { 313 write_unlock(&state->lock); 314 return -ENOSPC; 315 } 316 317 state->ranges[slot].gpa_start = op.gpa_start; 318 state->ranges[slot].gpa_end = op.gpa_start + op.length; 319 state->ranges[slot].active = true; 320 state->ranges[slot].original_type = p2m_ram_rw; 321 322 if ( (unsigned int)slot >= state->num_ranges ) 323 state->num_ranges = slot + 1; 324 325 write_unlock(&state->lock); 326 327 return 0; 328} 329 330static long turnstile_unprotect_range(struct domain *d, 331 XEN_GUEST_HANDLE_PARAM(void) arg) 332{ 333 struct turnstile_domain_state *state = get_turnstile_state(d); 334 struct turnstile_op_protect_range op; 335 unsigned int i; 336 bool found = false; 337 338 if ( !state ) 339 return -EINVAL; 340 341 if ( copy_from_guest(&op, arg, 1) ) 342 return -EFAULT; 343 344 write_lock(&state->lock); 345 346 for ( i = 0; i < state->num_ranges; i++ ) 347 { 348 if ( state->ranges[i].active && 349 state->ranges[i].gpa_start == op.gpa_start ) 350 { 351 state->ranges[i].active = false; 352 found = true; 353 break; 354 } 355 } 356 357 write_unlock(&state->lock); 358 359 return found ? 0 : -ENOENT; 360} 361 362static long turnstile_request_write(struct domain *d, 363 XEN_GUEST_HANDLE_PARAM(void) arg) 364{ 365 struct turnstile_domain_state *state = get_turnstile_state(d); 366 struct turnstile_op_request_write op; 367 368 if ( !state ) 369 return -EINVAL; 370 371 if ( copy_from_guest(&op, arg, 1) ) 372 return -EFAULT; 373 374 if ( op.timeout_ms > 1000 ) 375 return -EINVAL; 376 377 if ( op.length == 0 ) 378 return -EINVAL; 379 380 write_lock(&state->lock); 381 382 if ( !gpa_in_range(state, op.gpa_start) ) 383 { 384 write_unlock(&state->lock); 385 return -ENOENT; 386 } 387 388 state->write_grant.gpa_start = op.gpa_start; 389 state->write_grant.gpa_end = op.gpa_start + op.length; 390 state->write_grant.expiry = NOW() + MILLISECS(op.timeout_ms); 391 state->write_grant.active = true; 392 393 write_unlock(&state->lock); 394 395 return 0; 396} 397 398static long turnstile_get_violations(struct domain *d, 399 XEN_GUEST_HANDLE_PARAM(void) arg) 400{ 401 struct turnstile_domain_state *state = get_turnstile_state(d); 402 struct turnstile_op_get_violations op; 403 void *buf_ptr; 404 uint32_t count = 0; 405 406 if ( !state ) 407 return -EINVAL; 408 409 if ( copy_from_guest(&op, arg, 1) ) 410 return -EFAULT; 411 412 if ( op.max_entries == 0 ) 413 return -EINVAL; 414 415 buf_ptr = (void *)(unsigned long)op.buffer_ptr; 416 417 write_lock(&state->lock); 418 419 while ( state->ring_tail != state->ring_head && count < op.max_entries ) 420 { 421 struct turnstile_violation *v = &state->ring[state->ring_tail]; 422 void *dest = buf_ptr + (count * sizeof(struct turnstile_violation)); 423 424 if ( raw_copy_to_guest(dest, v, sizeof(struct turnstile_violation)) ) 425 { 426 write_unlock(&state->lock); 427 return -EFAULT; 428 } 429 430 state->ring_tail = (state->ring_tail + 1) % TURNSTILE_RING_SIZE; 431 count++; 432 } 433 434 op.entries_returned = count; 435 op.overflow_count = state->overflow_count; 436 state->overflow_count = 0; 437 438 write_unlock(&state->lock); 439 440 if ( copy_to_guest(arg, &op, 1) ) 441 return -EFAULT; 442 443 return 0; 444} 445 446static long turnstile_get_stats(struct domain *d, 447 XEN_GUEST_HANDLE_PARAM(void) arg) 448{ 449 struct turnstile_domain_state *state = get_turnstile_state(d); 450 struct turnstile_stats stats; 451 452 if ( !state ) 453 return -EINVAL; 454 455 read_lock(&state->lock); 456 stats = state->stats; 457 read_unlock(&state->lock); 458 459 if ( copy_to_guest(arg, &stats, 1) ) 460 return -EFAULT; 461 462 return 0; 463} 464 465static long turnstile_upload_metadata(struct domain *d, 466 XEN_GUEST_HANDLE_PARAM(void) arg) 467{ 468 struct turnstile_domain_state *state = get_turnstile_state(d); 469 struct turnstile_op_upload_metadata op; 470 void *buf_ptr; 471 uint64_t *new_entries; 472 unsigned int alloc_count; 473 474 if ( !state ) 475 return -EINVAL; 476 477 if ( copy_from_guest(&op, arg, 1) ) 478 return -EFAULT; 479 480 if ( op.count == 0 ) 481 return -EINVAL; 482 483 if ( op.metadata_type == TURNSTILE_META_FUNC_ENTRIES ) 484 { 485 if ( op.count > TURNSTILE_MAX_FUNC_ENTRIES ) 486 return -EINVAL; 487 488 alloc_count = op.count; 489 } 490 else if ( op.metadata_type == TURNSTILE_META_JUMP_ENTRIES ) 491 { 492 if ( op.count > TURNSTILE_MAX_JUMP_ENTRIES ) 493 return -EINVAL; 494 495 alloc_count = op.count * 3; 496 } 497 else 498 { 499 return -EINVAL; 500 } 501 502 new_entries = xmalloc_array(uint64_t, alloc_count); 503 if ( !new_entries ) 504 return -ENOMEM; 505 506 buf_ptr = (void *)(unsigned long)op.buffer_ptr; 507 508 if ( raw_copy_from_guest(new_entries, buf_ptr, alloc_count * sizeof(uint64_t)) ) 509 { 510 xfree(new_entries); 511 return -EFAULT; 512 } 513 514 write_lock(&state->lock); 515 516 if ( op.metadata_type == TURNSTILE_META_FUNC_ENTRIES ) 517 { 518 xfree(state->func_entries); 519 state->func_entries = new_entries; 520 state->num_func_entries = op.count; 521 } 522 else 523 { 524 xfree(state->jump_entries); 525 state->jump_entries = new_entries; 526 state->num_jump_entries = op.count; 527 } 528 529 write_unlock(&state->lock); 530 531 return 0; 532} 533 534long do_turnstile_op(unsigned int op, domid_t domid, XEN_GUEST_HANDLE_PARAM(void) arg) 535{ 536 struct domain *d; 537 long ret; 538 bool self = false; 539 540 if ( domid == DOMID_SELF ) 541 { 542 if ( op != TURNSTILE_OP_REQUEST_WRITE ) 543 return -EPERM; 544 545 d = current->domain; 546 get_knownalive_domain(d); 547 self = true; 548 } 549 else 550 { 551 if ( !is_control_domain(current->domain) ) 552 return -EPERM; 553 554 d = get_domain_by_id(domid); 555 if ( !d ) 556 return -ESRCH; 557 } 558 559 if ( !is_hvm_domain(d) ) 560 { 561 put_domain(d); 562 return -EINVAL; 563 } 564 565 if ( !get_turnstile_state(d) ) 566 { 567 if ( self ) 568 { 569 put_domain(d); 570 return -EINVAL; 571 } 572 573 ret = turnstile_domain_init(d); 574 if ( ret ) 575 { 576 put_domain(d); 577 return ret; 578 } 579 } 580 581 switch ( op ) 582 { 583 case TURNSTILE_OP_SET_POLICY: 584 ret = turnstile_set_policy(d, arg); 585 break; 586 587 case TURNSTILE_OP_PROTECT_RANGE: 588 ret = turnstile_protect_range(d, arg); 589 break; 590 591 case TURNSTILE_OP_UNPROTECT_RANGE: 592 ret = turnstile_unprotect_range(d, arg); 593 break; 594 595 case TURNSTILE_OP_REQUEST_WRITE: 596 ret = turnstile_request_write(d, arg); 597 break; 598 599 case TURNSTILE_OP_GET_VIOLATIONS: 600 ret = turnstile_get_violations(d, arg); 601 break; 602 603 case TURNSTILE_OP_GET_STATS: 604 ret = turnstile_get_stats(d, arg); 605 break; 606 607 case TURNSTILE_OP_UPLOAD_METADATA: 608 ret = turnstile_upload_metadata(d, arg); 609 break; 610 611 default: 612 ret = -ENOSYS; 613 } 614 615 put_domain(d); 616 return ret; 617}