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

Re: [PATCH v4] xen: introduce CONFIG_HAS_SHARED_INFO for archs without a shared page





On 6/29/26 4:26 PM, Jan Beulich wrote:
On 25.06.2026 18:02, Oleksii Kurochko wrote:
On architectures that run guests in dom0less mode without the PV ABI
(currently RISC-V), no shared_info page is allocated and d->shared_info
remains NULL throughout the domain lifetime.  Several places in common
code access d->shared_info through the shared_info() macro or directly,
causing UBSAN null-pointer errors on such architectures.

Rather than adding runtime NULL guards that are logically unreachable
on x86 and Arm (where shared_info is always allocated), introduce a new
Kconfig symbol CONFIG_HAS_SHARED_INFO selected by x86 and Arm.

On !HAS_SHARED_INFO the shared_info() macro expands to a dereference
of a pointer returned by shared_info_absent(), which is declared but
intentionally never defined.

This looks to need updating.

I will update it to:

On !HAS_SHARED_INFO the shared_info() macro expands to a dereference of shared_info_absent, an extern pointer that is declared but intentionally never defined.


  Any use of shared_info() that is not
dead-code-eliminated will therefore cause a link-time failure, making
missed guards impossible to overlook.

The 2L event-channel ops call shared_info() and must not be compiled on
architectures without a shared_info page, so event_2l.o is gated on
CONFIG_HAS_SHARED_INFO.  On such architectures evtchn_init() installs
the FIFO ops as a placeholder instead; evtchn_fifo_word_from_port() is
guarded against uninitialised d->evtchn_fifo so the FIFO ops are safe
before evtchn_fifo_init_control() is called by the guest.

With CONFIG_HAS_SHARED_INFO=n all vCPUs fall back to the global
dummy_vcpu_info, so writes through vcpu_info() could leak data between
vCPUs. Reviewing the write paths in common code: the write in
map_guest_area() stores the constant ~0 so nothing serious would happen
if it were leaked; the event_2l.c paths are unreachable because the
preceding shared_info() call would trap first;

Why "trap"? You can't build an image that way, can you?

"trap" was shorthand for the link-time failure.

I will changed that part to:
... leaked; the event_2l.c paths are not compiled on !HAS_SHARED_INFO, as event_2l.o is gated on CONFIG_HAS_SHARED_INFO; ...


@@ -1624,7 +1626,11 @@ void evtchn_check_pollers(struct domain *d, unsigned int 
port)
int evtchn_init(struct domain *d, unsigned int max_port)
  {
-    evtchn_2l_init(d);
+    if ( IS_ENABLED(CONFIG_HAS_SHARED_INFO) )
+        evtchn_2l_init(d);

For this to build when !HAS_SHARED_INFO, all you need is a declaration of
the function. The compiler will DCE the call. Hence ...

--- a/xen/common/event_channel.h
+++ b/xen/common/event_channel.h
@@ -44,7 +44,11 @@ static inline void evtchn_port_print_state(struct domain *d,
/* 2-level */ +#ifdef CONFIG_HAS_SHARED_INFO
  void evtchn_2l_init(struct domain *d);
+#else
+static inline void evtchn_2l_init(struct domain *d) {}
+#endif
/* FIFO */

... this hunk should be unnecessary?

Looks like you are right, I will double-check that.


@@ -55,6 +59,7 @@ struct evtchn_expand_array;
  int evtchn_fifo_init_control(struct evtchn_init_control *init_control);
  int evtchn_fifo_expand_array(const struct evtchn_expand_array *expand_array);
  void evtchn_fifo_destroy(struct domain *d);
+void evtchn_fifo_init_ops(struct domain *d);
  #else
  static inline int evtchn_fifo_init_control(struct evtchn_init_control 
*init_control)
  {
@@ -68,6 +73,7 @@ static inline void evtchn_fifo_destroy(struct domain *d)
  {
      return;
  }
+static inline void evtchn_fifo_init_ops(struct domain *d) {}
  #endif /* CONFIG_EVTCHN_FIFO */

Unlike these two. Which raise a different question though: What will be the
behavior when EVTCHN_FIFO=n and HAS_SHARED_INFO=n? Taking
evtchn_alloc_unbound() as example, afaict evtchn_port_init() will stumble
over a NULL pointer. Looks like for that (and only that) case we still need
your earlier dummy fallback.

I will introduce dummy fallback (I will shrunk some stubs in final version):

+#ifndef CONFIG_HAS_SHARED_INFO
+static void cf_check evtchn_none_set_pending(
+    struct vcpu *v, struct evtchn *evtchn) {}
+static void cf_check evtchn_none_clear_pending(
+    struct domain *d, struct evtchn *evtchn) {}
+static void cf_check evtchn_none_unmask(
+    struct domain *d, struct evtchn *evtchn) {}
+static bool cf_check evtchn_none_is_pending(
+    const struct domain *d, const struct evtchn *evtchn) { return false; }
+static bool cf_check evtchn_none_is_masked(
+    const struct domain *d, const struct evtchn *evtchn) { return true; }
+static void cf_check evtchn_none_print_state(
+    struct domain *d, const struct evtchn *evtchn) {}
+
+static const struct evtchn_port_ops evtchn_port_ops_none = {
+    .set_pending   = evtchn_none_set_pending,
+    .clear_pending = evtchn_none_clear_pending,
+    .unmask        = evtchn_none_unmask,
+    .is_pending    = evtchn_none_is_pending,
+    .is_masked     = evtchn_none_is_masked,
+    .print_state   = evtchn_none_print_state,
+};
+
+static void evtchn_none_init(struct domain *d)
+{
+    d->evtchn_port_ops = &evtchn_port_ops_none;
+}
+#endif


@@ -420,6 +423,11 @@ static const struct evtchn_port_ops evtchn_port_ops_fifo =
      .print_state   = evtchn_fifo_print_state,
  };
+void evtchn_fifo_init_ops(struct domain *d)
+{
+    d->evtchn_port_ops = &evtchn_port_ops_fifo;
+}

Isn't this unreachable code when HAS_SHARED_INFO=y, violating Misra rule 2.1?
I think if we are going to return back to dummy fallback I think that we could drop evtchn_fifo_init_ops() as d->evtchn_port_ops will be initialized later for FIFO. And then in evtchn_init:

int evtchn_init(struct domain *d, unsigned int max_port)
{
    if ( IS_ENABLED(CONFIG_HAS_SHARED_INFO) )
        evtchn_2l_init(d);
    else
        evtchn_none_init(d);

(and I expect evtchn_none_init() will be just dropped by compiler if CONFIG_HAS_SHARED_INFO=y so nothing extra should be done)

Thanks.

~ Oleksii



 


Rackspace

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