* [PATCH v2 0/2] xen/sched: fix crashes when vcpu creation fails @ 2026-08-19 5:15 Furkan Caliskan 2026-08-19 5:15 ` [PATCH v2 1/2] xen/sched: core: skip missing vcpu slots in sched_move_domain() Furkan Caliskan 2026-08-19 5:15 ` [PATCH v2 2/2] xen/sched: core: kill unarmed timers on sched_init_vcpu() failure Furkan Caliskan 0 siblings, 2 replies; 12+ messages in thread From: Furkan Caliskan @ 2026-08-19 5:15 UTC (permalink / raw) To: xen-devel Cc: jgross, jbeulich, andrew.cooper3, dfaggioli, gwd, Furkan Caliskan Furkan Caliskan (2): xen/sched: core: skip missing vcpu slots in sched_move_domain() xen/sched: core: kill unarmed timers on sched_init_vcpu() failure xen/common/sched/core.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) -- 2.34.1 ^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v2 1/2] xen/sched: core: skip missing vcpu slots in sched_move_domain() 2026-08-19 5:15 [PATCH v2 0/2] xen/sched: fix crashes when vcpu creation fails Furkan Caliskan @ 2026-08-19 5:15 ` Furkan Caliskan 2026-08-19 6:53 ` Jan Beulich 2026-08-19 5:15 ` [PATCH v2 2/2] xen/sched: core: kill unarmed timers on sched_init_vcpu() failure Furkan Caliskan 1 sibling, 1 reply; 12+ messages in thread From: Furkan Caliskan @ 2026-08-19 5:15 UTC (permalink / raw) To: xen-devel Cc: jgross, jbeulich, andrew.cooper3, dfaggioli, gwd, Furkan Caliskan sched_move_domain() derives the number of units to rebuild from d->max_vcpus, which is fixed at domain creation and never rolled back if vcpu_create() fails partway through building a domain. So d->vcpu[i] can be NULL for some i even though max_vcpus still counts it - this happens if sched_alloc_udata() returns NULL. The per-unit loop doesn't check for this: it sets unit->vcpu_list = d->vcpu[unit_id] (NULL) and hands that broken unit straight to the destination scheduler's alloc_udata(), which assumes vcpu_list is always valid and crashes Xen when it is not. Reproduced by building a domain in a non-default cpupool where vcpu creation fails partway through, then destroying it. domain_kill() moves the domain back to the default cpupool via sched_move_domain() before actually destroying it, crashing inside the destination scheduler's alloc_udata() (seen in Credit2's csched2_alloc_udata() -> is_idle_unit() -> NULL deref). Before building a unit in sched_move_domain(), check whether all vpcu slots belonging to that unit are populated. If any of its vpcus is missing: - For a dying domain, skip the unit allocation. - For an active domain, abort the move and return -EINVAL to prevent running with dropped vCPUs. Fixes: 70fadc41635b ("xen/cpupool: support moving domain between cpupools with different granularity") Signed-off-by: Furkan Caliskan <frn1furkan10@gmail.com> --- v2: - Fail with -EINVAL if vcpu slots are missing in an active domain. - Added Fixes: tag. --- xen/common/sched/core.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/xen/common/sched/core.c b/xen/common/sched/core.c index d3a0a97e1d..a9daa42339 100644 --- a/xen/common/sched/core.c +++ b/xen/common/sched/core.c @@ -745,6 +745,38 @@ int sched_move_domain(struct domain *d, struct cpupool *c) for ( unit_idx = 0; unit_idx < n_units; unit_idx++ ) { + /* + * A vcpu slot can be missing if creation failed partway + * through. A dying domain is being torn down regardless, so + * skip the unit -- but a domain that isn't dying still needs + * every vcpu it has schedulable, so fail instead of silently + * dropping some of them. + */ + bool vcpu_failed = false; + + for ( unsigned int i = 0; + i < gran && unit_idx * gran + i < d->max_vcpus; i++ ) + { + if ( !d->vcpu[unit_idx * gran + i] ) + { + vcpu_failed = true; + break; + } + } + + if ( vcpu_failed ) + { + if ( !d->is_dying ) + { + sched_move_domain_cleanup(c->sched, new_units, domdata); + rcu_read_unlock(&sched_res_rculock); + + return -EINVAL; + } + + continue; + } + unit = sched_alloc_unit_mem(); if ( unit ) { -- 2.34.1 ^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH v2 1/2] xen/sched: core: skip missing vcpu slots in sched_move_domain() 2026-08-19 5:15 ` [PATCH v2 1/2] xen/sched: core: skip missing vcpu slots in sched_move_domain() Furkan Caliskan @ 2026-08-19 6:53 ` Jan Beulich 2026-08-19 7:28 ` Furkan Çalışkan 0 siblings, 1 reply; 12+ messages in thread From: Jan Beulich @ 2026-08-19 6:53 UTC (permalink / raw) To: Furkan Caliskan; +Cc: jgross, andrew.cooper3, dfaggioli, gwd, xen-devel On 19.08.2026 07:15, Furkan Caliskan wrote: > --- a/xen/common/sched/core.c > +++ b/xen/common/sched/core.c > @@ -745,6 +745,38 @@ int sched_move_domain(struct domain *d, struct cpupool *c) > > for ( unit_idx = 0; unit_idx < n_units; unit_idx++ ) > { > + /* > + * A vcpu slot can be missing if creation failed partway > + * through. A dying domain is being torn down regardless, so > + * skip the unit -- but a domain that isn't dying still needs > + * every vcpu it has schedulable, so fail instead of silently > + * dropping some of them. > + */ > + bool vcpu_failed = false; > + > + for ( unsigned int i = 0; > + i < gran && unit_idx * gran + i < d->max_vcpus; i++ ) > + { > + if ( !d->vcpu[unit_idx * gran + i] ) Is there a particular reason domain_vcpu() cannot be used here? Jan ^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v2 1/2] xen/sched: core: skip missing vcpu slots in sched_move_domain() 2026-08-19 6:53 ` Jan Beulich @ 2026-08-19 7:28 ` Furkan Çalışkan 2026-08-19 7:35 ` Jan Beulich 0 siblings, 1 reply; 12+ messages in thread From: Furkan Çalışkan @ 2026-08-19 7:28 UTC (permalink / raw) To: Jan Beulich; +Cc: jgross, andrew.cooper3, dfaggioli, gwd, xen-devel On 8/19/26 09:53, Jan Beulich wrote: > On 19.08.2026 07:15, Furkan Caliskan wrote: >> --- a/xen/common/sched/core.c >> +++ b/xen/common/sched/core.c >> @@ -745,6 +745,38 @@ int sched_move_domain(struct domain *d, struct cpupool *c) >> >> for ( unit_idx = 0; unit_idx < n_units; unit_idx++ ) >> { >> + /* >> + * A vcpu slot can be missing if creation failed partway >> + * through. A dying domain is being torn down regardless, so >> + * skip the unit -- but a domain that isn't dying still needs >> + * every vcpu it has schedulable, so fail instead of silently >> + * dropping some of them. >> + */ >> + bool vcpu_failed = false; >> + >> + for ( unsigned int i = 0; >> + i < gran && unit_idx * gran + i < d->max_vcpus; i++ ) >> + { >> + if ( !d->vcpu[unit_idx * gran + i] ) > > Is there a particular reason domain_vcpu() cannot be used here? > > Jan We still need to guard against d->max_vcpus so that out-of-bounds indices in a partially filled unit don't get treated as missing vCPUs by domain_vcpu() returning NULL. However, domain_vcpu(unit_idx * gran + i) can be used here instead of d->vcpu[unit_idx * gran + i]. I simply used d->vcpu[] because the rest of the function uses it that way. Furkan ^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v2 1/2] xen/sched: core: skip missing vcpu slots in sched_move_domain() 2026-08-19 7:28 ` Furkan Çalışkan @ 2026-08-19 7:35 ` Jan Beulich 0 siblings, 0 replies; 12+ messages in thread From: Jan Beulich @ 2026-08-19 7:35 UTC (permalink / raw) To: Furkan Çalışkan Cc: jgross, andrew.cooper3, dfaggioli, gwd, xen-devel On 19.08.2026 09:28, Furkan Çalışkan wrote: > > > On 8/19/26 09:53, Jan Beulich wrote: >> On 19.08.2026 07:15, Furkan Caliskan wrote: >>> --- a/xen/common/sched/core.c >>> +++ b/xen/common/sched/core.c >>> @@ -745,6 +745,38 @@ int sched_move_domain(struct domain *d, struct cpupool *c) >>> >>> for ( unit_idx = 0; unit_idx < n_units; unit_idx++ ) >>> { >>> + /* >>> + * A vcpu slot can be missing if creation failed partway >>> + * through. A dying domain is being torn down regardless, so >>> + * skip the unit -- but a domain that isn't dying still needs >>> + * every vcpu it has schedulable, so fail instead of silently >>> + * dropping some of them. >>> + */ >>> + bool vcpu_failed = false; >>> + >>> + for ( unsigned int i = 0; >>> + i < gran && unit_idx * gran + i < d->max_vcpus; i++ ) >>> + { >>> + if ( !d->vcpu[unit_idx * gran + i] ) >> >> Is there a particular reason domain_vcpu() cannot be used here? > > We still need to guard against d->max_vcpus so that out-of-bounds > indices in a partially filled unit don't get treated as missing > vCPUs by domain_vcpu() returning NULL. Ah, right - the bounds check cannot really be folded here. Then ... > However, domain_vcpu(unit_idx * gran + i) can be used here instead of > d->vcpu[unit_idx * gran + i]. I simply used d->vcpu[] because the rest > of the function uses it that way. ... best wait for Jürgen to comment. Outside of the scheduler we're trying to replace open-coding of domain_vcpu(), but inside the scheduler things may be different. Jan ^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v2 2/2] xen/sched: core: kill unarmed timers on sched_init_vcpu() failure 2026-08-19 5:15 [PATCH v2 0/2] xen/sched: fix crashes when vcpu creation fails Furkan Caliskan 2026-08-19 5:15 ` [PATCH v2 1/2] xen/sched: core: skip missing vcpu slots in sched_move_domain() Furkan Caliskan @ 2026-08-19 5:15 ` Furkan Caliskan 2026-08-19 7:22 ` Jan Beulich 1 sibling, 1 reply; 12+ messages in thread From: Furkan Caliskan @ 2026-08-19 5:15 UTC (permalink / raw) To: xen-devel Cc: jgross, jbeulich, andrew.cooper3, dfaggioli, gwd, Furkan Caliskan sched_init_vcpu() calls init_timer() for a vcpu's periodic_timer, singleshot_timer and poll_timer before it can fail -- these become live, linked into their target pCPU's per-cpu timer list regardless of what happens next. If the sched_alloc_udata() call further down then fails, the function frees the sched_unit via sched_free_unit() and returns 1, but never unlinks these three timers. The caller, vcpu_create(), makes this worse: on sched_init_vcpu() returning nonzero it jumps to fail_wq, skipping fail_sched and thus sched_destroy_vcpu() -- the only function on this path that calls kill_timer() on them. vcpu_destroy() then frees the vcpu, and the three timers embedded in it, while they are still linked into that shared list. This silently corrupts that list. It only shows up later, when something else touches a neighboring timer: sched_move_domain() crashed with "Assertion 'entry->prev->next == entry' failed" on a completely unrelated, valid vcpu's timer. Kill all three timers in sched_init_vcpu()'s own failure branch, so it doesn't depend on the caller reaching sched_destroy_vcpu() to undo what it set up itself. Fixes: 1ad5dad74cde ("[XEN] Re-jig VCPU initialisation -- VMX init requires generic VCPU") Signed-off-by: Furkan Caliskan <frn1furkan10@gmail.com> Reviewed-by: Juergen Gross <jgross@suse.com> --- v2: - Added Fixes: tag. --- xen/common/sched/core.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/xen/common/sched/core.c b/xen/common/sched/core.c index a9daa42339..5777096592 100644 --- a/xen/common/sched/core.c +++ b/xen/common/sched/core.c @@ -589,6 +589,9 @@ int sched_init_vcpu(struct vcpu *v) unit->priv = sched_alloc_udata(dom_scheduler(d), unit, d->sched_priv); if ( unit->priv == NULL ) { + kill_timer(&v->periodic_timer); + kill_timer(&v->singleshot_timer); + kill_timer(&v->poll_timer); sched_free_unit(unit, v); rcu_read_unlock(&sched_res_rculock); return 1; -- 2.34.1 ^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH v2 2/2] xen/sched: core: kill unarmed timers on sched_init_vcpu() failure 2026-08-19 5:15 ` [PATCH v2 2/2] xen/sched: core: kill unarmed timers on sched_init_vcpu() failure Furkan Caliskan @ 2026-08-19 7:22 ` Jan Beulich 2026-08-19 7:53 ` Furkan Çalışkan 2026-08-19 10:39 ` Furkan Çalışkan 0 siblings, 2 replies; 12+ messages in thread From: Jan Beulich @ 2026-08-19 7:22 UTC (permalink / raw) To: Furkan Caliskan; +Cc: jgross, andrew.cooper3, dfaggioli, gwd, xen-devel On 19.08.2026 07:15, Furkan Caliskan wrote: > sched_init_vcpu() calls init_timer() for a vcpu's periodic_timer, > singleshot_timer and poll_timer before it can fail -- these > become live, linked into their target pCPU's per-cpu timer list > regardless of what happens next. If the sched_alloc_udata() call > further down then fails, the function frees the sched_unit via > sched_free_unit() and returns 1, but never unlinks these three > timers. > > The caller, vcpu_create(), makes this worse: on sched_init_vcpu() > returning nonzero it jumps to fail_wq, skipping fail_sched and > thus sched_destroy_vcpu() -- the only function on this path that > calls kill_timer() on them. vcpu_destroy() then frees the vcpu, > and the three timers embedded in it, while they are still linked > into that shared list. > > This silently corrupts that list. It only shows up later, when > something else touches a neighboring timer: sched_move_domain() > crashed with "Assertion 'entry->prev->next == entry' failed" on a > completely unrelated, valid vcpu's timer. > > Kill all three timers in sched_init_vcpu()'s own failure branch, > so it doesn't depend on the caller reaching sched_destroy_vcpu() > to undo what it set up itself. > > Fixes: 1ad5dad74cde ("[XEN] Re-jig VCPU initialisation -- VMX init requires generic VCPU") How did you arrive at this commit? It doesn't even touch sched_init_vcpu(). All it does is move kill_timer() invocations around. I think it's d884b1077817, as that's where the "return SCHED_OP(init_vcpu, v)" was introduced (i.e. where kill_timer() would have been necessary to call in the error case). (I can't exclude the issue was pre-existing already at that time, but that would require more analysis than I think is worth to invest.) > --- a/xen/common/sched/core.c > +++ b/xen/common/sched/core.c > @@ -589,6 +589,9 @@ int sched_init_vcpu(struct vcpu *v) > unit->priv = sched_alloc_udata(dom_scheduler(d), unit, d->sched_priv); > if ( unit->priv == NULL ) > { > + kill_timer(&v->periodic_timer); > + kill_timer(&v->singleshot_timer); > + kill_timer(&v->poll_timer); > sched_free_unit(unit, v); > rcu_read_unlock(&sched_res_rculock); > return 1; This almost, but not quite open-codes sched_destroy_vcpu(). Would be nice if the cleanup logic was shared. The sched_free_unit() call there could be leveraged here as well; what would need skipping are the sched_free_udata() and sched_remove_unit(). And of course the RCU-locking would need sorting. Jan ^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v2 2/2] xen/sched: core: kill unarmed timers on sched_init_vcpu() failure 2026-08-19 7:22 ` Jan Beulich @ 2026-08-19 7:53 ` Furkan Çalışkan 2026-08-19 8:32 ` Jan Beulich 2026-08-19 10:39 ` Furkan Çalışkan 1 sibling, 1 reply; 12+ messages in thread From: Furkan Çalışkan @ 2026-08-19 7:53 UTC (permalink / raw) To: Jan Beulich; +Cc: jgross, andrew.cooper3, dfaggioli, gwd, xen-devel On 8/19/26 10:22, Jan Beulich wrote: > On 19.08.2026 07:15, Furkan Caliskan wrote: >> sched_init_vcpu() calls init_timer() for a vcpu's periodic_timer, >> singleshot_timer and poll_timer before it can fail -- these >> become live, linked into their target pCPU's per-cpu timer list >> regardless of what happens next. If the sched_alloc_udata() call >> further down then fails, the function frees the sched_unit via >> sched_free_unit() and returns 1, but never unlinks these three >> timers. >> >> The caller, vcpu_create(), makes this worse: on sched_init_vcpu() >> returning nonzero it jumps to fail_wq, skipping fail_sched and >> thus sched_destroy_vcpu() -- the only function on this path that >> calls kill_timer() on them. vcpu_destroy() then frees the vcpu, >> and the three timers embedded in it, while they are still linked >> into that shared list. >> >> This silently corrupts that list. It only shows up later, when >> something else touches a neighboring timer: sched_move_domain() >> crashed with "Assertion 'entry->prev->next == entry' failed" on a >> completely unrelated, valid vcpu's timer. >> >> Kill all three timers in sched_init_vcpu()'s own failure branch, >> so it doesn't depend on the caller reaching sched_destroy_vcpu() >> to undo what it set up itself. >> >> Fixes: 1ad5dad74cde ("[XEN] Re-jig VCPU initialisation -- VMX init requires generic VCPU") > > How did you arrive at this commit? It doesn't even touch sched_init_vcpu(). > All it does is move kill_timer() invocations around. I think it's > d884b1077817, as that's where the "return SCHED_OP(init_vcpu, v)" was > introduced (i.e. where kill_timer() would have been necessary to call in > the error case). (I can't exclude the issue was pre-existing already at > that time, but that would require more analysis than I think is worth to > invest.) Commit 1ad5dad74cde moved kill_timer() calls into sched_destroy_vcpu() function, which is not called if sched_init_vcpu() fails. Before that commit, kill_timer() calls were in sched_destroy_domain(), which is called if sched_init_vcpu() returns non-zero to its caller, alloc_vcpu(). for ( i = 0; i < max; i++ ) { if ( d->vcpu[i] != NULL ) continue; cpu = (i == 0) ? default_vcpu0_location() : (d->vcpu[i-1]->processor + 1) % num_online_cpus(); if ( alloc_vcpu(d, i, cpu) == NULL ) goto maxvcpu_out; } ret = 0; maxvcpu_out: domain_unpause(d); put_domain(d); } break; put_domain() calls domain_destroy(), which then calls free_domain(), which ultimately calls sched_destroy_domain(). > >> --- a/xen/common/sched/core.c >> +++ b/xen/common/sched/core.c >> @@ -589,6 +589,9 @@ int sched_init_vcpu(struct vcpu *v) >> unit->priv = sched_alloc_udata(dom_scheduler(d), unit, d->sched_priv); >> if ( unit->priv == NULL ) >> { >> + kill_timer(&v->periodic_timer); >> + kill_timer(&v->singleshot_timer); >> + kill_timer(&v->poll_timer); >> sched_free_unit(unit, v); >> rcu_read_unlock(&sched_res_rculock); >> return 1; > > This almost, but not quite open-codes sched_destroy_vcpu(). Would be nice > if the cleanup logic was shared. The sched_free_unit() call there could be > leveraged here as well; what would need skipping are the sched_free_udata() > and sched_remove_unit(). And of course the RCU-locking would need sorting. > > Jan Furkan ^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v2 2/2] xen/sched: core: kill unarmed timers on sched_init_vcpu() failure 2026-08-19 7:53 ` Furkan Çalışkan @ 2026-08-19 8:32 ` Jan Beulich 2026-08-19 8:50 ` Furkan Çalışkan 0 siblings, 1 reply; 12+ messages in thread From: Jan Beulich @ 2026-08-19 8:32 UTC (permalink / raw) To: Furkan Çalışkan Cc: jgross, andrew.cooper3, dfaggioli, gwd, xen-devel On 19.08.2026 09:53, Furkan Çalışkan wrote: > > > On 8/19/26 10:22, Jan Beulich wrote: >> On 19.08.2026 07:15, Furkan Caliskan wrote: >>> sched_init_vcpu() calls init_timer() for a vcpu's periodic_timer, >>> singleshot_timer and poll_timer before it can fail -- these >>> become live, linked into their target pCPU's per-cpu timer list >>> regardless of what happens next. If the sched_alloc_udata() call >>> further down then fails, the function frees the sched_unit via >>> sched_free_unit() and returns 1, but never unlinks these three >>> timers. >>> >>> The caller, vcpu_create(), makes this worse: on sched_init_vcpu() >>> returning nonzero it jumps to fail_wq, skipping fail_sched and >>> thus sched_destroy_vcpu() -- the only function on this path that >>> calls kill_timer() on them. vcpu_destroy() then frees the vcpu, >>> and the three timers embedded in it, while they are still linked >>> into that shared list. >>> >>> This silently corrupts that list. It only shows up later, when >>> something else touches a neighboring timer: sched_move_domain() >>> crashed with "Assertion 'entry->prev->next == entry' failed" on a >>> completely unrelated, valid vcpu's timer. >>> >>> Kill all three timers in sched_init_vcpu()'s own failure branch, >>> so it doesn't depend on the caller reaching sched_destroy_vcpu() >>> to undo what it set up itself. >>> >>> Fixes: 1ad5dad74cde ("[XEN] Re-jig VCPU initialisation -- VMX init requires generic VCPU") >> >> How did you arrive at this commit? It doesn't even touch sched_init_vcpu(). >> All it does is move kill_timer() invocations around. I think it's >> d884b1077817, as that's where the "return SCHED_OP(init_vcpu, v)" was >> introduced (i.e. where kill_timer() would have been necessary to call in >> the error case). (I can't exclude the issue was pre-existing already at >> that time, but that would require more analysis than I think is worth to >> invest.) > > Commit 1ad5dad74cde moved kill_timer() calls into sched_destroy_vcpu() > function, which is not called if sched_init_vcpu() fails. > Before that commit, kill_timer() calls were in sched_destroy_domain(), > which is called if sched_init_vcpu() returns non-zero to its caller, > alloc_vcpu(). > > for ( i = 0; i < max; i++ ) > { > if ( d->vcpu[i] != NULL ) > continue; > > cpu = (i == 0) ? > default_vcpu0_location() : > (d->vcpu[i-1]->processor + 1) % num_online_cpus(); > > if ( alloc_vcpu(d, i, cpu) == NULL ) > goto maxvcpu_out; > } > > ret = 0; > > maxvcpu_out: > domain_unpause(d); > put_domain(d); > } > break; > > put_domain() calls domain_destroy(), which then calls free_domain(), > which ultimately calls sched_destroy_domain(). Well, yes, except that - how would that have helped for a vCPU which failed to be properly constructed? The function loops over all vCPU-s in the domain, but that wouldn't include the vCPU in question. alloc_vcpu() would (of course) insert the vCPU into the list only when sched_init_vcpu() succeeds. Jan ^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v2 2/2] xen/sched: core: kill unarmed timers on sched_init_vcpu() failure 2026-08-19 8:32 ` Jan Beulich @ 2026-08-19 8:50 ` Furkan Çalışkan 0 siblings, 0 replies; 12+ messages in thread From: Furkan Çalışkan @ 2026-08-19 8:50 UTC (permalink / raw) To: Jan Beulich; +Cc: jgross, andrew.cooper3, dfaggioli, gwd, xen-devel On 8/19/26 11:32, Jan Beulich wrote: > On 19.08.2026 09:53, Furkan Çalışkan wrote: >> >> >> On 8/19/26 10:22, Jan Beulich wrote: >>> On 19.08.2026 07:15, Furkan Caliskan wrote: >>>> sched_init_vcpu() calls init_timer() for a vcpu's periodic_timer, >>>> singleshot_timer and poll_timer before it can fail -- these >>>> become live, linked into their target pCPU's per-cpu timer list >>>> regardless of what happens next. If the sched_alloc_udata() call >>>> further down then fails, the function frees the sched_unit via >>>> sched_free_unit() and returns 1, but never unlinks these three >>>> timers. >>>> >>>> The caller, vcpu_create(), makes this worse: on sched_init_vcpu() >>>> returning nonzero it jumps to fail_wq, skipping fail_sched and >>>> thus sched_destroy_vcpu() -- the only function on this path that >>>> calls kill_timer() on them. vcpu_destroy() then frees the vcpu, >>>> and the three timers embedded in it, while they are still linked >>>> into that shared list. >>>> >>>> This silently corrupts that list. It only shows up later, when >>>> something else touches a neighboring timer: sched_move_domain() >>>> crashed with "Assertion 'entry->prev->next == entry' failed" on a >>>> completely unrelated, valid vcpu's timer. >>>> >>>> Kill all three timers in sched_init_vcpu()'s own failure branch, >>>> so it doesn't depend on the caller reaching sched_destroy_vcpu() >>>> to undo what it set up itself. >>>> >>>> Fixes: 1ad5dad74cde ("[XEN] Re-jig VCPU initialisation -- VMX init requires generic VCPU") >>> >>> How did you arrive at this commit? It doesn't even touch sched_init_vcpu(). >>> All it does is move kill_timer() invocations around. I think it's >>> d884b1077817, as that's where the "return SCHED_OP(init_vcpu, v)" was >>> introduced (i.e. where kill_timer() would have been necessary to call in >>> the error case). (I can't exclude the issue was pre-existing already at >>> that time, but that would require more analysis than I think is worth to >>> invest.) >> >> Commit 1ad5dad74cde moved kill_timer() calls into sched_destroy_vcpu() >> function, which is not called if sched_init_vcpu() fails. >> Before that commit, kill_timer() calls were in sched_destroy_domain(), >> which is called if sched_init_vcpu() returns non-zero to its caller, >> alloc_vcpu(). >> >> for ( i = 0; i < max; i++ ) >> { >> if ( d->vcpu[i] != NULL ) >> continue; >> >> cpu = (i == 0) ? >> default_vcpu0_location() : >> (d->vcpu[i-1]->processor + 1) % num_online_cpus(); >> >> if ( alloc_vcpu(d, i, cpu) == NULL ) >> goto maxvcpu_out; >> } >> >> ret = 0; >> >> maxvcpu_out: >> domain_unpause(d); >> put_domain(d); >> } >> break; >> >> put_domain() calls domain_destroy(), which then calls free_domain(), >> which ultimately calls sched_destroy_domain(). > > Well, yes, except that - how would that have helped for a vCPU which > failed to be properly constructed? The function loops over all vCPU-s > in the domain, but that wouldn't include the vCPU in question. > alloc_vcpu() would (of course) insert the vCPU into the list only when > sched_init_vcpu() succeeds. > > Jan Ah, I missed that compeletely - you're right. sched_destroy_domain() wouldn't have reached the failed vCPU anyway. In that case, d884b1077817 makes total sense here. Furkan ^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v2 2/2] xen/sched: core: kill unarmed timers on sched_init_vcpu() failure 2026-08-19 7:22 ` Jan Beulich 2026-08-19 7:53 ` Furkan Çalışkan @ 2026-08-19 10:39 ` Furkan Çalışkan 2026-08-19 10:43 ` Jan Beulich 1 sibling, 1 reply; 12+ messages in thread From: Furkan Çalışkan @ 2026-08-19 10:39 UTC (permalink / raw) To: Jan Beulich; +Cc: jgross, andrew.cooper3, dfaggioli, gwd, xen-devel On 8/19/26 10:22, Jan Beulich wrote: > On 19.08.2026 07:15, Furkan Caliskan wrote: >> sched_init_vcpu() calls init_timer() for a vcpu's periodic_timer, >> singleshot_timer and poll_timer before it can fail -- these >> become live, linked into their target pCPU's per-cpu timer list >> regardless of what happens next. If the sched_alloc_udata() call >> further down then fails, the function frees the sched_unit via >> sched_free_unit() and returns 1, but never unlinks these three >> timers. >> >> The caller, vcpu_create(), makes this worse: on sched_init_vcpu() >> returning nonzero it jumps to fail_wq, skipping fail_sched and >> thus sched_destroy_vcpu() -- the only function on this path that >> calls kill_timer() on them. vcpu_destroy() then frees the vcpu, >> and the three timers embedded in it, while they are still linked >> into that shared list. >> >> This silently corrupts that list. It only shows up later, when >> something else touches a neighboring timer: sched_move_domain() >> crashed with "Assertion 'entry->prev->next == entry' failed" on a >> completely unrelated, valid vcpu's timer. >> >> Kill all three timers in sched_init_vcpu()'s own failure branch, >> so it doesn't depend on the caller reaching sched_destroy_vcpu() >> to undo what it set up itself. >> >> Fixes: 1ad5dad74cde ("[XEN] Re-jig VCPU initialisation -- VMX init requires generic VCPU") > > How did you arrive at this commit? It doesn't even touch sched_init_vcpu(). > All it does is move kill_timer() invocations around. I think it's > d884b1077817, as that's where the "return SCHED_OP(init_vcpu, v)" was > introduced (i.e. where kill_timer() would have been necessary to call in > the error case). (I can't exclude the issue was pre-existing already at > that time, but that would require more analysis than I think is worth to > invest.) > >> --- a/xen/common/sched/core.c >> +++ b/xen/common/sched/core.c >> @@ -589,6 +589,9 @@ int sched_init_vcpu(struct vcpu *v) >> unit->priv = sched_alloc_udata(dom_scheduler(d), unit, d->sched_priv); >> if ( unit->priv == NULL ) >> { >> + kill_timer(&v->periodic_timer); >> + kill_timer(&v->singleshot_timer); >> + kill_timer(&v->poll_timer); >> sched_free_unit(unit, v); >> rcu_read_unlock(&sched_res_rculock); >> return 1; > > This almost, but not quite open-codes sched_destroy_vcpu(). Would be nice > if the cleanup logic was shared. The sched_free_unit() call there could be > leveraged here as well; what would need skipping are the sched_free_udata() > and sched_remove_unit(). And of course the RCU-locking would need sorting. > > Jan Would something like below be okay? diff --git a/xen/common/sched/core.c b/xen/common/sched/core.c index d3a0a97e1d..36704a5836 100644 --- a/xen/common/sched/core.c +++ b/xen/common/sched/core.c @@ -589,8 +589,8 @@ int sched_init_vcpu(struct vcpu *v) unit->priv = sched_alloc_udata(dom_scheduler(d), unit, d->sched_priv); if ( unit->priv == NULL ) { - sched_free_unit(unit, v); rcu_read_unlock(&sched_res_rculock); + sched_destroy_vcpu(v); return 1; } @@ -869,8 +869,11 @@ void sched_destroy_vcpu(struct vcpu *v) { rcu_read_lock(&sched_res_rculock); - sched_remove_unit(vcpu_scheduler(v), unit); - sched_free_udata(vcpu_scheduler(v), unit->priv); + if ( unit->priv ) + { + sched_remove_unit(vcpu_scheduler(v), unit); + sched_free_udata(vcpu_scheduler(v), unit->priv); + } sched_free_unit(unit, v); rcu_read_unlock(&sched_res_rculock); Furkan ^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH v2 2/2] xen/sched: core: kill unarmed timers on sched_init_vcpu() failure 2026-08-19 10:39 ` Furkan Çalışkan @ 2026-08-19 10:43 ` Jan Beulich 0 siblings, 0 replies; 12+ messages in thread From: Jan Beulich @ 2026-08-19 10:43 UTC (permalink / raw) To: Furkan Çalışkan Cc: jgross, andrew.cooper3, dfaggioli, gwd, xen-devel On 19.08.2026 12:39, Furkan Çalışkan wrote: > On 8/19/26 10:22, Jan Beulich wrote: >> On 19.08.2026 07:15, Furkan Caliskan wrote: >>> --- a/xen/common/sched/core.c >>> +++ b/xen/common/sched/core.c >>> @@ -589,6 +589,9 @@ int sched_init_vcpu(struct vcpu *v) >>> unit->priv = sched_alloc_udata(dom_scheduler(d), unit, d->sched_priv); >>> if ( unit->priv == NULL ) >>> { >>> + kill_timer(&v->periodic_timer); >>> + kill_timer(&v->singleshot_timer); >>> + kill_timer(&v->poll_timer); >>> sched_free_unit(unit, v); >>> rcu_read_unlock(&sched_res_rculock); >>> return 1; >> >> This almost, but not quite open-codes sched_destroy_vcpu(). Would be nice >> if the cleanup logic was shared. The sched_free_unit() call there could be >> leveraged here as well; what would need skipping are the sched_free_udata() >> and sched_remove_unit(). And of course the RCU-locking would need sorting. > > Would something like below be okay? Maybe, but you need to ask the maintainers of this code, which I'm not a part of. What I in particular can't easily judge is whether ... > --- a/xen/common/sched/core.c > +++ b/xen/common/sched/core.c > @@ -589,8 +589,8 @@ int sched_init_vcpu(struct vcpu *v) > unit->priv = sched_alloc_udata(dom_scheduler(d), unit, d->sched_priv); > if ( unit->priv == NULL ) > { > - sched_free_unit(unit, v); > rcu_read_unlock(&sched_res_rculock); > + sched_destroy_vcpu(v); > return 1; > } ... this intermediate dropping of the lock is entirely okay (it looks to be at the first glance). Jan > @@ -869,8 +869,11 @@ void sched_destroy_vcpu(struct vcpu *v) > { > rcu_read_lock(&sched_res_rculock); > > - sched_remove_unit(vcpu_scheduler(v), unit); > - sched_free_udata(vcpu_scheduler(v), unit->priv); > + if ( unit->priv ) > + { > + sched_remove_unit(vcpu_scheduler(v), unit); > + sched_free_udata(vcpu_scheduler(v), unit->priv); > + } > sched_free_unit(unit, v); > > rcu_read_unlock(&sched_res_rculock); > > > Furkan ^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-08-19 10:44 UTC | newest] Thread overview: 12+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-08-19 5:15 [PATCH v2 0/2] xen/sched: fix crashes when vcpu creation fails Furkan Caliskan 2026-08-19 5:15 ` [PATCH v2 1/2] xen/sched: core: skip missing vcpu slots in sched_move_domain() Furkan Caliskan 2026-08-19 6:53 ` Jan Beulich 2026-08-19 7:28 ` Furkan Çalışkan 2026-08-19 7:35 ` Jan Beulich 2026-08-19 5:15 ` [PATCH v2 2/2] xen/sched: core: kill unarmed timers on sched_init_vcpu() failure Furkan Caliskan 2026-08-19 7:22 ` Jan Beulich 2026-08-19 7:53 ` Furkan Çalışkan 2026-08-19 8:32 ` Jan Beulich 2026-08-19 8:50 ` Furkan Çalışkan 2026-08-19 10:39 ` Furkan Çalışkan 2026-08-19 10:43 ` Jan Beulich
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.