When the function entry point is switched, the old stack frames currently executing do not jump into the new implementation. The core of livepatch is compatibility between old and new logic during the transition, not merely redirecting the entry point.
It applies to fixes that have been specifically designed and verified; it is not a general-purpose online version upgrader. Patches must still match the target build and configuration.
Review Objective
Reading Location
When task switching is allowed
Function Entry, Task Consistency, and Transition
How state continues to be compatible
Cumulative Patches, State Lifecycle
How to stop and verify
Disable/Unload, PREEMPT_RT Boundaries
Function Entry and Task Consistency
Common implementations rely on dynamic ftrace to redirect at the function entry point, with the livepatch handler selecting the appropriate version based on the task's patch state, among other factors. Stack frames currently executing an old function will not automatically jump into the middle of the new implementation just because the entry point was changed.
The kernel uses a consistency model to determine when tasks can be switched. Reliable stack checks can identify whether a task is still within an affected call chain, and returning to user space or other safe boundaries also help advance the state; kernel threads that remain for a long time or paths lacking reliable stack information may hinder completion. Livepatch
It is incorrect to generalize the implementation into three fictional generic enum modes: IDLE, FORCE, and IMMEDIATE. In reality, one should understand the interface states such as enabled, transition, and force, along with the specific kernel model.
Before Transition Completion, Old and New Versions May Coexist
Transition State
What the Patch Must Be Compatible With
Task A has switched to the new version
New logic accessing shared state
Task B is still in the old call chain
Old layout and old lock ordering may still be used
All related tasks complete the transition
Only then can subsequent stages be handled according to completion conditions
Shared data formats and cross-function invariants must cover the coexistence phase, not just be designed for the final state.
If a task long blocks the transition, analyze its stack and wait reasons. Auxiliary wake-ups or signals do not equate to being able to force the safe evacuation of any currently executing function. force bypasses corresponding safety guarantees and affects subsequent unloading capabilities; it should not be treated as a simple timeout retry button.
Why A and B Temporarily Choose Different Versions at the Same Entry
Assume the patch updates both update and lookup. Task A has the old update in its stack while sleeping, while Task B's reliable sleep stack does not contain these two functions. Task A temporarily retains the old patch state, while Task B can switch first; subsequent calls to redirection still need to select based on each task's state. Otherwise, Task A might suddenly call the new lookup within an old call chain, breaking the conventions maintained by both.
The model below demonstrates only reliable sleep stack checks and return-to-user-space boundaries. It assumes affected fully lists the functions that need to be replaced together, and does not simulate ftrace, preemption, stack unwinding, or kernel state bits.
fromdataclassesimportdataclass@dataclassclassTask:version:intstack:tuplesleeping:bool=Truereliable:bool=Truedeftry_stack_switch(self,affected,target):ifnotself.sleepingornotself.reliable:returnFalseifany(functioninaffectedforfunctioninself.stack):returnFalseself.version=targetreturnTruedefreturn_to_user(self,target):self.stack=()self.version=target# This method represents having reached the user-space return boundary
defimplementation(self,name):returnf"{name}_v{self.version}"affected={"update","lookup"}a=Task(0,("request","update"))b=Task(0,("unrelated_wait",))c=Task(0,(),reliable=False)assertnota.try_stack_switch(affected,1)assertb.try_stack_switch(affected,1)assertnotc.try_stack_switch(affected,1)asserta.implementation("lookup")=="lookup_v0"assertb.implementation("lookup")=="lookup_v1"assertnotall(t.version==1fortin(a,b,c))a.stack=("request",)# Old update has returned; other stack frames in this example are unaffected
asserta.try_stack_switch(affected,1)c.return_to_user(1)assertall(t.version==1fortin(a,b,c))assertnotTask(0,(),sleeping=False).try_stack_switch(affected,1)
Although Task C's stack list is empty, "unreliable unwinding didn't see the target function" does not equal "reliably proven that the target function does not exist." Therefore, the model refuses to switch based solely on that result and waits for another known safe boundary. The actual protocol must also prevent races between the check and the task continuing execution; user space cannot read /proc/PID/stack once and then arbitrarily rewrite the task's patch state. Livepatch Consistency Model
These per-task rules do not automatically solve shared data compatibility across tasks. Patch authors must also check whether the old logic of A and the new logic of B satisfy lock ordering, object interpretation, and call contracts when running simultaneously. Transition completion is a necessary state indicator and cannot replace these semantic conditions.
Keep one task inside the old function while enabling the patch. Existing frames retain their version, and reverse transitions and unloading have their own waiting conditions.
Preparing the visual
Does changing the target version move an existing stack frame?
Entry redirection does not rewrite active frames. Task versions can coexist during transition; disabling requires a reverse transition before patch-code unloading.
{"id":"livepatch-task-transition","title":"Does changing the target version move an existing stack frame?","summary":"Entry redirection does not rewrite active frames. Task versions can coexist during transition; disabling requires a reverse transition before patch-code unloading.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"actions\"><button id=\"enterA\" data-i18n=\"enterA\"></button><button id=\"returnA\" data-i18n=\"returnA\"></button></div><div class=\"actions\"><button id=\"enterB\" data-i18n=\"enterB\"></button><button id=\"returnB\" data-i18n=\"returnB\"></button></div><div class=\"actions\"><button id=\"enable\" data-i18n=\"enable\"></button><button id=\"disable\" data-i18n=\"disable\"></button></div><div class=\"track-row\"><span data-i18n=\"target\"></span><div id=\"target\" class=\"track\"></div></div><div class=\"track-row\"><span data-i18n=\"tasks\"></span><div id=\"tasks\" class=\"track\"></div></div><div class=\"card\"><header data-i18n=\"status\"></header><div id=\"status\" class=\"readout\"></div></div><div class=\"actions\"><button id=\"unload\" data-i18n=\"unload\"></button></div><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"note\"></p></div><div class=\"actions\"><button id=\"reset\" data-i18n=\"reset\"></button></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n#tasks{display:grid;grid-template-columns:1fr}#tasks .cell{min-height:65px}.readout{min-height:5.3em}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}let target=0,versions=[0,0],frames=[null,null],loaded=true;const name=v=>v?'new':'old';function settle(){for(let i=0;i<2;i++)if(frames[i]===null)versions[i]=target;}function draw(){settle();const transition=versions.some(v=>v!==target);cells('target',[{text:name(target),cls:target?'gold':'on'}]);cells('tasks',versions.map((v,i)=>({text:'AB'[i]+': '+name(v)+' → '+(frames[i]===null?'—':name(frames[i])),cls:v===target?'on':'gold'})));for(let i=0;i<2;i++){$('#enter'+'AB'[i]).disabled=frames[i]!==null;$('#return'+'AB'[i]).disabled=frames[i]===null;}$('#enable').disabled=!loaded||transition||target===1;$('#disable').disabled=!loaded||transition||target===0;$('#unload').disabled=!loaded||transition||target===1||frames.includes(1);set('status',t(!loaded?'unloaded':transition?'waiting':'stable'));set('explanation',t('note'));}for(let i=0;i<2;i++){$('#enter'+'AB'[i]).onclick=()=>{frames[i]=versions[i];draw();};$('#return'+'AB'[i]).onclick=()=>{frames[i]=null;draw();};}$('#enable').onclick=()=>{target=1;draw();};$('#disable').onclick=()=>{target=0;draw();};$('#unload').onclick=()=>{loaded=false;draw();};$('#reset').onclick=()=>{target=0;versions=[0,0];frames=[null,null];loaded=true;draw();};draw();","audio":false,"strings":{"disable":"Request patch disable","enable":"Request patch enable","enterA":"A enters affected function","enterB":"B enters affected function","heading":"Does changing the target version move an existing stack frame?","intro":"Enter/return from affected functions in tasks A/B, then enable or disable the patch. Each task retains its version until its affected call stack permits transition.","note":"Let A enter old, then enable: B can adopt new while A’s active frame stays old. A’s return completes the transition; changing the target cannot move a frame into the middle of new code.","reset":"Reset","returnA":"A returns to safe boundary","returnB":"B returns to safe boundary","scope":"Two tasks, one patch, no nesting. Conservative stack model: a task switches only after an affected frame exits; empty-stack tasks adopt the target immediately. Target changes require the previous transition to complete. Force, cumulative patches, and all real safe boundaries are omitted. Reset reloads an unloaded model patch.","scopeLabel":"Model scope and assumptions","stable":"Transition complete; patch code loaded","status":"Transition / code lifetime","target":"Target version","tasks":"Task version → active frame version","unload":"Unload disabled patch","unloaded":"Patch code unloaded","waiting":"Transitioning; old task state remains"}}
Cumulative Patches and Atomic Replace
Overlapping multiple independent patches increases the complexity of ordering and compatibility. Cumulative patches can include a set of existing fixes, using atomic replace to substitute the previous patch set during a consistent transition.
Here, atomic does not mean all CPUs rewrite all instructions at the exact same instant, nor does it provide arbitrary data rollback like a database transaction. It refers to the mechanism for replacing the patch set, which still requires completing task conversion and maintaining system state. Livepatch Documentation Index
What It Means If the New Cumulative Patch Does Not List g
Suppose the original kernel provides f and g; P1 fixes f, and P2 fixes g. Now use P3 with replace semantics to replace the old set, but P3 only contains the new implementation of f. After the transition completes, g will revert to the original implementation, rather than automatically retaining P2's fix.
Completed Configuration
What f Uses
What g Uses
P1 and P2 are active
P1's f
P2's g
P3 contains only f, replacing the old set
P3's f
Original g
P3 contains both required f and g fixes
P3's f
P3's g
base={"f":"base_f","g":"base_g"}p1={"f":"fix_f1"}p2={"g":"fix_g1"}p3={"f":"fix_f2"}old_active=base|p1|p2replacement=base|p3assertold_active["g"]=="fix_g1"assertreplacement=={"f":"fix_f2","g":"base_g"}p3_cumulative={"f":"fix_f2","g":"fix_g1"}assert(base|p3_cumulative)["g"]=="fix_g1"# Only compares the function set after completion; does not simulate transition or callbacks.
disabled_replacement=base.copy()assertdisabled_replacement!=old_active
Therefore, cumulative patches must include all fixes that still need to be retained. Disabling the new patch does not automatically atomically restore the old patch set; when rolling back, you must prepare a replacement scheme compatible with the current state.
Furthermore, atomic replace primarily executes the relevant callbacks of the new cumulative patch; it does not execute cleanup for each replaced patch in the imagined order. The new patch needs to take over necessary shared state; when the old module exits, it cannot blindly release shadow variables that the new patch is still using. Atomic Replace and Cumulative Patches
Stateful Fixes Require Explicit Lifecycle Design
You cannot simply replace a structure layout currently in use with any new layout; old objects still exist according to the original layout. However, stating that "no stateful patches are possible" is also inaccurate: shadow variables, callbacks, and system-state tracking can support designed state changes.
Patches need to handle first enablement, replacing old patches, undoing transitions, disabling, and failure cleanup. The id/version in klp_state are used to track compatible states and do not automatically understand how business objects are converted. System State Changes
Even With Unchanged Field Widths, State Migration May Be Required
Suppose an old object's timeout=100 represents 100 ms, and the new code wants to change the same integer to be in microseconds. Even if the structure size, field offsets, and function signatures remain unchanged, the two sets of code interpret the same bit pattern differently:
Object Content and Reader
Actual Interpretation
Difference from Original 100 ms
Old object value 100, read by new code as μs
0.1 ms
Shortened by 1000x
New code writes 100000, read by old code as ms
100 s
Extended by 1000x
Simply waiting for all tasks to complete the code transition will not automatically change the 100 in existing objects to 100000. During the transition, the old field can be retained as the common authoritative representation, allowing the new code to convert at boundaries; to change the persistent representation, compatible version identifiers, object update protocols, and appropriate conversion timing are required. Additional state tools provide storage and tracking mechanisms but do not decide the conversion order or rollback semantics for the author. Livepatch System State Changes
When Replacing Functions, the Caller's Contract Must Also Hold
Function signatures and calling conventions must be compatible with retained callers. When a fix involves parameters, object lifecycles, or cross-module calls, the scope may be far larger than replacing a single function; it cannot be considered safe just because redirection is possible.
Disable and Unload Are Not the Same Step
Regular patches can request disablement and complete the reverse transition, and then unload the relevant module once conditions are met; the statement "livepatch modules can never be unloaded" is an overgeneralization. States such as still in use, transition not complete, or forced transition will restrict unloading, and actual interface conditions must be followed.
Read-only observation can start with the enabled and transition attributes in each patch directory under /sys/kernel/livepatch. The absence of this directory or entry may indicate that support is not enabled or the patch is not loaded; this cannot be used to infer whether the system has fixed a specific vulnerability; distributions may also fix vulnerabilities through regular kernel updates.
PREEMPT_RT Is a Separate Issue
PREEMPT_RT primarily reduces scheduling latency caused by non-preemptible sections, involving mechanisms such as threaded interrupts, sleepable locks, and priority inheritance. It is not a mode of livepatch, nor can all preempt-disable operations be generalized as replacements with rt_mutex. Real-Time Kernel Mechanisms
Modern mainline kernels provide PREEMPT_RT configuration capabilities; architecture support and driver constraints still need to be checked against the current target. Do not retain outdated time descriptions like "6.12 is close to merge completion," nor make unsourced promises that a specific minor version solves all livepatch/RT interactions.
Verifying hotpatching should cover the target workload, transition completion, disablement, and state compatibility paths. Successful builds or sysfs showing enabled do not replace functional regression testing; related methods can be found in Debugging and Testing.