mirror of
https://github.com/xen-project/xen.git
synced 2026-10-28 02:01:24 +01:00
xen: introduce CONFIG_HAS_SHARED_INFO for archs without a shared page
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 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, so that a later guest opt-in to the FIFO ABI via EVTCHNOP_init_control has no special-casing to do; if FIFO support itself is also unavailable (!CONFIG_EVTCHN_FIFO), a dedicated no-op evtchn_port_ops_none table is installed instead, so that d->evtchn_port_ops is never NULL. 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 not compiled on !HAS_SHARED_INFO, as event_2l.o is gated on CONFIG_HAS_SHARED_INFO; the write in vcpu_info_populate() targets the new mapping buffer, not dummy_vcpu_info. Outside common code, the remaining writes are x86 PV-specific, for which CONFIG_HAS_SHARED_INFO=y. No code changes are needed. Finally, struct domain's shared_info field itself is gated on CONFIG_HAS_SHARED_INFO, as it would otherwise be a permanently NULL pointer: every user of it is either arch code for an architecture that selects HAS_SHARED_INFO, or common code already guarded by the same Kconfig symbol. Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com> Reviewed-by: Jan Beulich <jbeulich@suse.com> Acked-by: Michal Orzel <michal.orzel@amd.com> #Arm
This commit is contained in:
1 parent
68c3f7fb3a
commit
31fb44f8c0
14 files changed
+103
-8
No files matched your search
@@ -20,6 +20,7 @@ config ARM
|
||||
select HAS_DEVICE_TREE_DISCOVERY
|
||||
select HAS_DOM0LESS
|
||||
select HAS_GRANT_CACHE_FLUSH if GRANT_TABLE
|
||||
select HAS_SHARED_INFO
|
||||
select HAS_STACK_PROTECTOR
|
||||
select HAS_STATIC_MEMORY
|
||||
select HAS_UBSAN
|
||||
|
||||
@@ -29,6 +29,7 @@ config X86
|
||||
select HAS_PCI_MSI
|
||||
select HAS_PIRQ
|
||||
select HAS_SCHED_GRANULARITY
|
||||
select HAS_SHARED_INFO
|
||||
imply HAS_SOFT_RESET
|
||||
select HAS_UBSAN
|
||||
select HAS_VMAP
|
||||
|
||||
@@ -158,6 +158,9 @@ config HAS_PMAP
|
||||
config HAS_SCHED_GRANULARITY
|
||||
bool
|
||||
|
||||
config HAS_SHARED_INFO
|
||||
bool
|
||||
|
||||
config HAS_STATIC_MEMORY
|
||||
bool
|
||||
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ obj-$(CONFIG_DEVICE_TREE_PARSE) += device-tree/
|
||||
obj-$(CONFIG_IOREQ_SERVER) += dm.o
|
||||
obj-y += domain.o
|
||||
obj-y += domid.o
|
||||
obj-y += event_2l.o
|
||||
obj-$(CONFIG_HAS_SHARED_INFO) += event_2l.o
|
||||
obj-y += event_channel.o
|
||||
obj-$(CONFIG_EVTCHN_FIFO) += event_fifo.o
|
||||
obj-$(CONFIG_GRANT_TABLE) += grant_table.o
|
||||
|
||||
+2
-2
@@ -316,9 +316,9 @@ void vcpu_info_reset(struct vcpu *v)
|
||||
struct domain *d = v->domain;
|
||||
|
||||
v->vcpu_info_area.map =
|
||||
((v->vcpu_id < XEN_LEGACY_MAX_VCPUS)
|
||||
IS_ENABLED(CONFIG_HAS_SHARED_INFO) && v->vcpu_id < XEN_LEGACY_MAX_VCPUS
|
||||
? (vcpu_info_t *)&shared_info(d, vcpu_info[v->vcpu_id])
|
||||
: &dummy_vcpu_info);
|
||||
: &dummy_vcpu_info;
|
||||
}
|
||||
|
||||
static struct domain *alloc_domain_struct(void)
|
||||
|
||||
@@ -102,9 +102,13 @@ void getdomaininfo(struct domain *d, struct xen_domctl_getdomaininfo *info)
|
||||
#ifdef CONFIG_MEM_PAGING
|
||||
info->paged_pages = atomic_read(&d->paged_pages);
|
||||
#endif
|
||||
#ifdef CONFIG_HAS_SHARED_INFO
|
||||
info->shared_info_frame =
|
||||
gfn_x(mfn_to_gfn(d, _mfn(virt_to_mfn(d->shared_info))));
|
||||
BUG_ON(SHARED_M2P(info->shared_info_frame));
|
||||
#else
|
||||
info->shared_info_frame = ~0;
|
||||
#endif
|
||||
|
||||
info->cpupool = cpupool_get_id(d);
|
||||
|
||||
|
||||
@@ -40,6 +40,54 @@
|
||||
|
||||
#define consumer_is_xen(e) (!!(e)->xen_consumer)
|
||||
|
||||
#if !defined(CONFIG_HAS_SHARED_INFO) && !defined(CONFIG_EVTCHN_FIFO)
|
||||
/*
|
||||
* Placeholder ops for domains with neither a shared_info page nor a FIFO
|
||||
* control block. Such a domain has no ABI to record event state in, so these
|
||||
* are reachable whenever an event is delivered to (or queried on) one of its
|
||||
* ports; they just discard/no-op it. They exist to keep d->evtchn_port_ops
|
||||
* non-NULL.
|
||||
*/
|
||||
static void cf_check evtchn_none_set_pending(
|
||||
struct vcpu *v, struct evtchn *evtchn) {}
|
||||
static void cf_check evtchn_none_noop(
|
||||
struct domain *d, struct evtchn *evtchn) {}
|
||||
static bool cf_check evtchn_none_false(
|
||||
const struct domain *d, const struct evtchn *evtchn) { return false; }
|
||||
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_noop,
|
||||
.unmask = evtchn_none_noop,
|
||||
.is_pending = evtchn_none_false,
|
||||
.is_masked = evtchn_none_false,
|
||||
.print_state = evtchn_none_print_state,
|
||||
};
|
||||
|
||||
static void evtchn_none_init(struct domain *d)
|
||||
{
|
||||
d->evtchn_port_ops = &evtchn_port_ops_none;
|
||||
}
|
||||
#else /* CONFIG_HAS_SHARED_INFO || CONFIG_EVTCHN_FIFO */
|
||||
/*
|
||||
* Declaration only; the call in evtchn_preinit() is DCE'd unless both
|
||||
* configs are off.
|
||||
*/
|
||||
void evtchn_none_init(struct domain *d);
|
||||
#endif /* !CONFIG_HAS_SHARED_INFO && !CONFIG_EVTCHN_FIFO */
|
||||
|
||||
static void evtchn_preinit(struct domain *d)
|
||||
{
|
||||
if ( IS_ENABLED(CONFIG_HAS_SHARED_INFO) )
|
||||
evtchn_2l_init(d);
|
||||
else if ( IS_ENABLED(CONFIG_EVTCHN_FIFO) )
|
||||
evtchn_fifo_init_ops(d);
|
||||
else
|
||||
evtchn_none_init(d);
|
||||
}
|
||||
|
||||
/*
|
||||
* Lock an event channel exclusively. This is allowed only when the channel is
|
||||
* free or unbound either when taking or when releasing the lock, as any
|
||||
@@ -1324,9 +1372,9 @@ int evtchn_reset(struct domain *d, bool resuming)
|
||||
rc = -EAGAIN;
|
||||
else if ( d->evtchn_fifo )
|
||||
{
|
||||
/* Switching back to 2-level ABI. */
|
||||
/* Switching back to the default ABI. */
|
||||
evtchn_fifo_destroy(d);
|
||||
evtchn_2l_init(d);
|
||||
evtchn_preinit(d);
|
||||
}
|
||||
|
||||
write_unlock(&d->event_lock);
|
||||
@@ -1625,7 +1673,8 @@ void evtchn_check_pollers(struct domain *d, unsigned int port)
|
||||
|
||||
int evtchn_init(struct domain *d, unsigned int max_port)
|
||||
{
|
||||
evtchn_2l_init(d);
|
||||
evtchn_preinit(d);
|
||||
|
||||
d->max_evtchn_port = min_t(unsigned int, max_port, INT_MAX);
|
||||
|
||||
d->evtchn = alloc_evtchn_bucket(d, 0);
|
||||
|
||||
@@ -70,6 +70,12 @@ static inline void evtchn_fifo_destroy(struct domain *d)
|
||||
}
|
||||
#endif /* CONFIG_EVTCHN_FIFO */
|
||||
|
||||
/*
|
||||
* Declaration only when !CONFIG_EVTCHN_FIFO; the call in evtchn_preinit() is
|
||||
* DCE'd in that case.
|
||||
*/
|
||||
void evtchn_fifo_init_ops(struct domain *d);
|
||||
|
||||
#endif /* EVENT_CHANNEL_H */
|
||||
|
||||
/*
|
||||
|
||||
+17
-1
@@ -62,6 +62,9 @@ static inline event_word_t *evtchn_fifo_word_from_port(const struct domain *d,
|
||||
*/
|
||||
smp_rmb();
|
||||
|
||||
if ( unlikely(!d->evtchn_fifo) )
|
||||
return NULL;
|
||||
|
||||
if ( unlikely(port >= d->evtchn_fifo->num_evtchns) )
|
||||
return NULL;
|
||||
|
||||
@@ -419,6 +422,18 @@ static const struct evtchn_port_ops evtchn_port_ops_fifo =
|
||||
.print_state = evtchn_fifo_print_state,
|
||||
};
|
||||
|
||||
/*
|
||||
* evtchn_fifo_init_ops()'s only call site is the
|
||||
* IS_ENABLED(CONFIG_EVTCHN_FIFO) branch of evtchn_preinit(), which is never
|
||||
* reached on HAS_SHARED_INFO=y builds because of DCE.
|
||||
*/
|
||||
#ifndef CONFIG_HAS_SHARED_INFO
|
||||
void evtchn_fifo_init_ops(struct domain *d)
|
||||
{
|
||||
d->evtchn_port_ops = &evtchn_port_ops_fifo;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int map_guest_page(struct domain *d, uint64_t gfn, void **virt)
|
||||
{
|
||||
struct page_info *p;
|
||||
@@ -561,7 +576,8 @@ static void setup_ports(struct domain *d, unsigned int prev_evtchns)
|
||||
|
||||
evtchn = evtchn_from_port(d, port);
|
||||
|
||||
if ( guest_test_bit(d, port, &shared_info(d, evtchn_pending)) )
|
||||
if ( IS_ENABLED(CONFIG_HAS_SHARED_INFO) &&
|
||||
guest_test_bit(d, port, &shared_info(d, evtchn_pending)) )
|
||||
evtchn->pending = true;
|
||||
|
||||
evtchn_fifo_set_priority(d, evtchn, EVTCHN_FIFO_PRIORITY_DEFAULT);
|
||||
|
||||
@@ -98,6 +98,7 @@ struct tm gmtime(unsigned long t)
|
||||
return tbuf;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_HAS_SHARED_INFO
|
||||
void update_domain_wallclock_time(struct domain *d)
|
||||
{
|
||||
uint32_t *wc_version;
|
||||
@@ -126,6 +127,7 @@ void update_domain_wallclock_time(struct domain *d)
|
||||
|
||||
spin_unlock(&wc_lock);
|
||||
}
|
||||
#endif /* CONFIG_HAS_SHARED_INFO */
|
||||
|
||||
/* Set clock to <secs,usecs> after 00:00:00 UTC, 1 January, 1970. */
|
||||
void do_settime(u64 secs, unsigned int nsecs, u64 system_time_base)
|
||||
|
||||
@@ -211,7 +211,7 @@ static bool evtchn_usable(const struct evtchn *evtchn)
|
||||
|
||||
void evtchn_check_pollers(struct domain *d, unsigned int port);
|
||||
|
||||
/* Close all event channels and reset to 2-level ABI. */
|
||||
/* Close all event channels and reset to the default ABI. */
|
||||
int evtchn_reset(struct domain *d, bool resuming);
|
||||
|
||||
/*
|
||||
|
||||
@@ -404,7 +404,9 @@ struct domain
|
||||
|
||||
struct vcpu **vcpu;
|
||||
|
||||
#ifdef CONFIG_HAS_SHARED_INFO
|
||||
shared_info_t *shared_info; /* shared data area */
|
||||
#endif
|
||||
|
||||
rcu_read_lock_t rcu_lock;
|
||||
|
||||
|
||||
@@ -43,7 +43,13 @@ typedef struct vcpu_info vcpu_info_t;
|
||||
|
||||
extern vcpu_info_t dummy_vcpu_info;
|
||||
|
||||
#ifdef CONFIG_HAS_SHARED_INFO
|
||||
#define shared_info(d, field) __shared_info(d, (d)->shared_info, field)
|
||||
#else
|
||||
extern struct shared_info *shared_info_absent;
|
||||
#define shared_info(d, field) (((void)(d), shared_info_absent)->field)
|
||||
#endif /* CONFIG_HAS_SHARED_INFO */
|
||||
|
||||
#define vcpu_info(v, field) \
|
||||
__vcpu_info(v, (vcpu_info_t *)(v)->vcpu_info_area.map, field)
|
||||
|
||||
|
||||
@@ -72,7 +72,12 @@ extern bool NOW_good;
|
||||
#define version_update_begin(v) (((v) + 1) | 1)
|
||||
#define version_update_end(v) ((v) + 1)
|
||||
extern void update_vcpu_system_time(struct vcpu *v);
|
||||
|
||||
#ifdef CONFIG_HAS_SHARED_INFO
|
||||
extern void update_domain_wallclock_time(struct domain *d);
|
||||
#else
|
||||
static inline void update_domain_wallclock_time(struct domain *d) {}
|
||||
#endif
|
||||
|
||||
extern void do_settime(
|
||||
u64 secs, unsigned int nsecs, u64 system_time_base);
|
||||
|
||||