[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [RFC 3/5] xen/tracing: prepare a livepatch payload and its controling interface


  • To: bernhard.kaindl@xxxxxxxxxx
  • From: Ryo Takakura <takakura@xxxxxxxxxxxxx>
  • Date: Wed, 28 Oct 2026 17:20:17 +0900
  • Arc-authentication-results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=valinux.co.jp; dmarc=pass action=none header.from=valinux.co.jp; dkim=pass header.d=valinux.co.jp; arc=none
  • Arc-message-signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=LansMxIS3D33QokSM4Gv2GyVi2Xj0o/26ld45KmfJX8=; b=c8kIcj/PNVrLYJFy57mDV3mu+la2Rkk5IaH1Sbx2ZjLJiBUa4cKwZuU/SEDnLVTXZGrrPMviWAdSsdbvP/C+wdUjH/GKxIuQhhCM2A6O6/PggNzFSzLF1iDIvv8bSNld3xE3biL2sBwrNW6ouhLbDoxk/9Vttwbv8vNXZeilwgP77vtUOYqmthDzniJDKEMNetbxsQwCh0RRu8lSrcVZjISSkFKn4sm0zTVlYbmjG6z2aNlSur1/FS3gtY28Ks+6tH80Ew1jVjY65AwOQiwxr0OZD5nMY11mkjl67CykbyK5GVMd5437IaIRIEOzx0zoMjX6dTUspOLwGJrEuyAE5w==
  • Arc-seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=LkeWRuXyMUiywJD9+7426Mmed8xBTHhjiKhbwXU/tD5w8yxZOQUza593Mbl/h1QekfOyzyG4TlscrgefNSr+3FyuJsBZd5LpLnTGnco05eBgqQeNyVKDId+CPalueHbgHCQtIH9t3l0wPus04aofTaw0/7ytCgfm/V5QLYxhxCG6KeLrYNA9P00qciwxMLxiWCULWaIJGlhdl7ekd8rnBukEOS8IHTREq2D7+V/uscqxNkHcci1jrVy2f5WcpXmaI7K3CLctg7Xh/mqFpsyN46CMl3WBMc2VHbBZg8wBRWFVF4GVb8TiMqW8yh+C87W4t0ljOSbAOo67B/5VAk199Q==
  • Authentication-results: eu.smtp.expurgate.cloud; dkim=pass header.s=selector1 header.d=valinux.co.jp header.i="@valinux.co.jp" header.h="From:Date:Subject:Message-Id:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck"
  • Authentication-results: mx.microsoft.com 1; dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=valinux.co.jp;
  • Cc: xen-devel@xxxxxxxxxxxxxxxxxxxx, sstabellini@xxxxxxxxxx, julien@xxxxxxx, bertrand.marquis@xxxxxxx, michal.orzel@xxxxxxx, Volodymyr_Babchuk@xxxxxxxx, andrew.cooper3@xxxxxxxxxx, anthony.perard@xxxxxxxxxx, jbeulich@xxxxxxxx, roger@xxxxxxxxxxxxxx, ross.lagerwall@xxxxxxxxxx, jgross@xxxxxxxx
  • Delivery-date: Wed, 28 Oct 2026 08:20:46 +0000
  • List-id: Xen developer discussion <xen-devel.lists.xenproject.org>

On Sat, 24 Oct 2026 16:13:58 +0200, Bernhard Kaindl wrote:
>> --- /dev/null
>> +++ b/xen/common/hypfs_trace.c
>> @@ -0,0 +1,741 @@
>> +void trace_dispatch_callbacks(unsigned long ip, unsigned long parent_ip)
>> +{
>> +    livepatch_trace_func_t callback;
>> +
>> +    if ( !tracing_is_on() )
>> +        return;
>> +
>> +    if ( !trace_callback_registered )
>> +        return;
>> +
>> +    if ( !global_trace_func.old_addr )
>> +        return;
>> +
>> +    if ( (unsigned long)global_trace_func.old_addr != ip )
>> +        return;
>> +
>> +    callback = global_trace_func.new_addr;
>> +    callback((unsigned long)global_trace_func.old_addr, parent_ip);
>> +}
>
>What synchronizes this reader with callback removal on another CPU?

Testing !trace_callback_registered was the intended synchronization here...

>The corresponding part of trace_unregister_callback() is:
>
>> --- /dev/null
>> +++ b/xen/common/hypfs_trace.c
>> @@ -0,0 +1,741 @@
>> +int trace_unregister_callback(void)
>> +{
>> +    int rc = 0;
>> +
>> +    spin_lock(&trace_callback_lock);
>> [...]
>> +    trace_callback_registered = false;
>> +    global_trace_func.new_addr = NULL;
>> +
>> +out_unlock:
>> +    spin_unlock(&trace_callback_lock);
>
>Could CPU A pass the enabled and registered tests, then CPU B clear
>new_addr, before CPU A loads and calls it? Would that permit a NULL
>indirect call despite the writer's lock, since the reader does not
>take that lock?

... and yes, I think this can happen.

>I wondered whether the existing payload RCU region might cover this,
>but the new dispatch appears after its unlock:
>
>> --- a/xen/common/livepatch.c
>> +++ b/xen/common/livepatch.c
>> @@ -232,6 +236,11 @@ xen_livepatch_trace_dispatcher(unsigned long ip, 
>> unsigned long parent_ip)
>>  out:
>>      rcu_read_unlock(&rcu_payload_lock);
>> +
>> +#ifdef CONFIG_LIVEPATCH_TRACER
>> +    /* Call tracer callbacks. */
>> +    trace_dispatch_callbacks(ip, parent_ip);
>> +#endif
>
>Would an ordered publication and reader-lifetime protocol help here,
>including draining callbacks before unregister/reset completes? Could
>it cover registration as well, where the registered flag is currently
>set before the callback pointer? Merely checking the pointer for NULL
>would not seem to protect callback state already being used elsewhere.

Thank you for your suggestion.

I'll extend the existing payload RCU read-side region to cover the
callback dispatch and remove the registered flag entirely.

Do you think something like the following would work?

---- BEGIN IDEA ----
--- a/xen/common/hypfs_trace.c
+++ b/xen/common/hypfs_trace.c
@@ -135,17 +135,16 @@ void trace_dispatch_callbacks(unsigned long ip, unsigned 
long parent_ip)
     if ( !tracing_is_on() )
         return;

-    if ( !trace_callback_registered )
-        return;
-
     if ( !global_trace_func.old_addr )
         return;

     if ( arch_livepatch_trace_entry((unsigned long)global_trace_func.old_addr) 
!= ip )
         return;

-    callback = global_trace_func.new_addr;
-    callback((unsigned long)global_trace_func.old_addr, parent_ip);
+    callback = rcu_dereference(global_trace_func.new_addr);
+
+    if ( callback )
+        callback((unsigned long)global_trace_func.old_addr, parent_ip);
 }

 int trace_register_callback(livepatch_trace_func_t callback)
@@ -154,14 +153,13 @@ int trace_register_callback(livepatch_trace_func_t 
callback)

     spin_lock(&trace_callback_lock);

-    if ( trace_callback_registered )
+    if ( global_trace_func.new_addr )
     {
         rc = -EEXIST;
         goto out_unlock;
     }

-    trace_callback_registered = true;
-    global_trace_func.new_addr = callback;
+    rcu_assign_pointer(global_trace_func.new_addr, callback);

  out_unlock:
     spin_unlock(&trace_callback_lock);
@@ -175,14 +173,16 @@ int trace_unregister_callback(void)

     spin_lock(&trace_callback_lock);

-    if ( !trace_callback_registered )
+    if ( !global_trace_func.new_addr )
     {
         rc = -ENOENT;
         goto out_unlock;
     }

-    trace_callback_registered = false;
-    global_trace_func.new_addr = NULL;
+    rcu_assign_pointer(global_trace_func.new_addr, NULL);
+
+    /* Drain every callbacks before unregistering this tracer */
+    rcu_barrier();

 out_unlock:
     spin_unlock(&trace_callback_lock);
---- END IDEA ----

>Separately, could a pending filter apply survive a successful clear?

Yes, it could.
Currently, it only checks the immediate result of schedule_work()
and the applied state of the payload.

>> --- /dev/null
>> +++ b/xen/common/hypfs_trace.c
>> @@ -0,0 +1,741 @@
>> +    /* Schedule removal of the current patch. */
>> +    if ( global_trace_fstate.applied == LIVEPATCH_FUNC_APPLIED )
>> [...]
>> +    /* Clear the filter if no target function was requested. */
>> +    if ( !ip )
>> +    {
>> +        /*
>> +         * Keep global_trace_func.old_addr since livepatching
>> +         * might be in flight. Only clear global_trace.filter_name
>> +         * which will effectively update /tracing/set_function_filter.
>> +         */
>> +        global_trace.filter_name[0] = '\0';
>> +        return 0;
>> +    }
>
>If I select a function and then clear the filter before the trace-on
>worker runs, wouldn't applied still be LIVEPATCH_FUNC_NOT_APPLIED?

Yes.

>Could the clear therefore return success without canceling or removing
>anything, followed by the queued worker installing the original target
>while set_function_filter reports an empty filter?

Yes.

>Would it make sense to track pending work explicitly and either return
>busy or arrange a safe cancellation/removal before accepting the clear?

Yes, I agree tracking the pending work. I'll make sure the pending work
gets handled appropriately for the next version.
And I believe it should take care the issue you addressed concerning
trace_set_filter() as well.

>There seems to be a related completion-status question at the end of
>trace_set_filter():

Yes.

>> --- /dev/null
>> +++ b/xen/common/hypfs_trace.c
>> @@ -0,0 +1,741 @@
>> +    rc = livepatch_schedule_trace_on(payload, global_trace.func, ip);
>> +    if ( rc )
>> +        return rc;
>> +
>> +    strlcpy(global_trace.filter_name, name, 
>> sizeof(global_trace.filter_name));
>
>Am I missing a place where the controls consume payload->rc after the
>worker finishes?

No, you are right. There is currently no such place.

>The existing livepatch worker can fail during CPU
>rendezvous or application after submission has succeeded. Could the
>displayed name then describe a target that was never installed? The
>removal path clears the name before completion, which seems to allow
>the converse if removal fails.

Yes. The same issue stemming from overlooking the pending work.

>Could requested and installed state be kept separate until completion,
>with a way for the user to see the eventual result?

I agree that those two states should be kept separate.

I plan to prepare a new file, something like /set_function_status,
which user can track the state of the request, which is payload->rc.
And leave /set_function_filter for showing the installed function.

>What is the intended contract for changing current_tracer while
>tracing_on is still set?

I see that its inconsistent right now.
I think current_tracer should not be changed while tracing_on is set.

>> --- /dev/null
>> +++ b/xen/common/hypfs_trace.c
>> @@ -0,0 +1,741 @@
>> +    if ( prev->reset )
>> +    {
>> +        rc = prev->reset(&global_trace);
>> +        if ( rc )
>> +            return rc;
>> +    }
>> +
>> +    global_trace.current_trace = &nop_tracer;
>> +
>> +    if ( new->init )
>> +    {
>> +        rc = new->init(&global_trace);
>> +        if ( rc )
>> +            return rc;
>> +    }
>> +
>> +    global_trace.current_trace = new;
>
>Could a tracer change either be rejected while recording, or perform
>a stop-and-drain transition before reset and initialization?

I plan fix it by the former approach of "reject while recording".

>For a tracer using the optional start/stop hooks, wouldn't the present path
>leave recording reported as enabled without stopping the old tracer
>or starting the new one?

I missed this inconsistency as well.
I believe it should also be fixed by enforcing "reject while recording".

>What should tracing_on report if the new
>initialization fails and nop remains selected?

By enforcing "reject while recording", it should be reporting '0' as
tracing should already be disabled at this point.

>One more target-validation question: what excludes discarded init
>functions from filter selection after boot?

Thank you. This was missing as well.

>> --- /dev/null
>> +++ b/xen/common/hypfs_trace.c
>> @@ -0,0 +1,741 @@
>> +    if ( len )
>> +    {
>> +        ip = symbols_lookup_by_name(name);
>> +        if ( !ip || !is_patchable_function_entry(ip) )
>> +            return -EINVAL;
>> +    }
>
>The new linker table appears to retain the compiler-generated entries
>without filtering out init functions:
>
>> --- a/xen/arch/arm/xen.lds.S
>> +++ b/xen/arch/arm/xen.lds.S
>> @@ -61,6 +61,11 @@ SECTIONS
>> +       . = ALIGN(8);
>> +       __start_patchable_function_entries = .;
>> +       KEEP(*(__patchable_function_entries))
>> +       __stop_patchable_function_entries = .;

Yes, they still retain the init functions.

>For reference, the existing symbol_valid() in xen/tools/symbols.c
>retains symbols in init text too, and symbols_lookup_by_name() in
>xen/common/symbols.c returns matching addresses without an active-text
>check. Its non-FAST_SYMBOL_LOOKUP branch contains:
>
>>         if ( !strcmp(name, symname) )
>>             return addr;
>
>Meanwhile free_init_memory() in xen/arch/arm/mmu/setup.c removes the
>init mapping and returns those pages to the heap when appropriate:
>
>>     rc = destroy_xen_mappings((unsigned long)__init_begin,
>>                               (unsigned long)__init_end);
>> [...]
>>     if ( !using_static_heap )
>>     {
>>         init_domheap_pages(pa, pa + len);
>
>Could an init function's name and compiler entry therefore still pass
>this selection check after its code has been discarded? Since the Arm
>patcher writes through an alias of the original image's physical pages,
>could that write into memory which has already been reused?

Thank you for elaborating.
And yes, currently init functions could pass the check and be selected.

>Would checking active text before accepting the target address rule out
>this case? To illustrate, I think this could be addressed with a small
>change like:
>
>--- a/xen/common/hypfs_trace.c
>+++ b/xen/common/hypfs_trace.c
>@@ -3,6 +3,7 @@
> #include <xen/init.h>
> #include <xen/hypfs.h>
> #include <xen/hypfs_trace.h>
>+#include <xen/kernel.h>
> #include <xen/livepatch_payload.h>
> #include <xen/symbols.h>
> #include <xen/string.h>
>@@ -266,7 +267,8 @@ static int trace_set_filter(const char *name)
>     if ( len )
>     {
>         ip = symbols_lookup_by_name(name);
>-        if ( !ip || !is_patchable_function_entry(ip) )
>+        if ( !ip || !is_active_kernel_text(ip) ||
>+             !is_patchable_function_entry(ip) )
>             return -EINVAL;
>     }
> 
>
>Would this reject discarded init text before either scheduling path,
>using the same text-region safeguard as address-based symbols_lookup()?

Yes, I think this works.
I'll make the change for the next version.

>This only checks whether the target is currently active text. It does
>not validate reserved instruction bytes, reject overlapping livepatches,
>or establish the lifetime of a dynamically removable target.

Understood.
For the other concerns, please take a look at my reply to your feedback
on the 1/5 patch. I believe the check I added for the proposed
arch_livepatch_verify_entry() should address those concerns.

>The pending
>operation and callback-lifetime questions above would still need their
>own solution.
>
>Thanks for looking through these questions. Is there a lifecycle,
>completion, or target-lifetime guarantee elsewhere that I have missed?

Thank you for your feedback. You didn't miss anything.
I'll make sure they get fixed for the next version.

Sincerely,
Ryo Takakura



 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.