sctp: add a refcnt in sctp_stream_priorities to avoid a nested loop

[ Upstream commit 68ba446395 ]

With this refcnt added in sctp_stream_priorities, we don't need to
traverse all streams to check if the prio is used by other streams
when freeing one stream's prio in sctp_sched_prio_free_sid(). This
can avoid a nested loop (up to 65535 * 65535), which may cause a
stuck as Ying reported:

    watchdog: BUG: soft lockup - CPU#23 stuck for 26s! [ksoftirqd/23:136]
    Call Trace:
     <TASK>
     sctp_sched_prio_free_sid+0xab/0x100 [sctp]
     sctp_stream_free_ext+0x64/0xa0 [sctp]
     sctp_stream_free+0x31/0x50 [sctp]
     sctp_association_free+0xa5/0x200 [sctp]

Note that it doesn't need to use refcount_t type for this counter,
as its accessing is always protected under the sock lock.

v1->v2:
 - add a check in sctp_sched_prio_set to avoid the possible prio_head
   refcnt overflow.

Fixes: 9ed7bfc795 ("sctp: fix memory leak in sctp_stream_outq_migrate()")
Reported-by: Ying Xu <yinxu@redhat.com>
Acked-by: Marcelo Ricardo Leitner <marcelo.leitner@gmail.com>
Signed-off-by: Xin Long <lucien.xin@gmail.com>
Link: https://lore.kernel.org/r/825eb0c905cb864991eba335f4a2b780e543f06b.1677085641.git.lucien.xin@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
This commit is contained in:
Xin Long 2023-02-22 12:07:21 -05:00 committed by Greg Kroah-Hartman
parent d28467230d
commit 03c3a5584a
2 changed files with 22 additions and 31 deletions

View File

@ -1409,6 +1409,7 @@ struct sctp_stream_priorities {
/* The next stream in line */ /* The next stream in line */
struct sctp_stream_out_ext *next; struct sctp_stream_out_ext *next;
__u16 prio; __u16 prio;
__u16 users;
}; };
struct sctp_stream_out_ext { struct sctp_stream_out_ext {

View File

@ -25,6 +25,18 @@
static void sctp_sched_prio_unsched_all(struct sctp_stream *stream); static void sctp_sched_prio_unsched_all(struct sctp_stream *stream);
static struct sctp_stream_priorities *sctp_sched_prio_head_get(struct sctp_stream_priorities *p)
{
p->users++;
return p;
}
static void sctp_sched_prio_head_put(struct sctp_stream_priorities *p)
{
if (p && --p->users == 0)
kfree(p);
}
static struct sctp_stream_priorities *sctp_sched_prio_new_head( static struct sctp_stream_priorities *sctp_sched_prio_new_head(
struct sctp_stream *stream, int prio, gfp_t gfp) struct sctp_stream *stream, int prio, gfp_t gfp)
{ {
@ -38,6 +50,7 @@ static struct sctp_stream_priorities *sctp_sched_prio_new_head(
INIT_LIST_HEAD(&p->active); INIT_LIST_HEAD(&p->active);
p->next = NULL; p->next = NULL;
p->prio = prio; p->prio = prio;
p->users = 1;
return p; return p;
} }
@ -53,7 +66,7 @@ static struct sctp_stream_priorities *sctp_sched_prio_get_head(
*/ */
list_for_each_entry(p, &stream->prio_list, prio_sched) { list_for_each_entry(p, &stream->prio_list, prio_sched) {
if (p->prio == prio) if (p->prio == prio)
return p; return sctp_sched_prio_head_get(p);
if (p->prio > prio) if (p->prio > prio)
break; break;
} }
@ -70,7 +83,7 @@ static struct sctp_stream_priorities *sctp_sched_prio_get_head(
*/ */
break; break;
if (p->prio == prio) if (p->prio == prio)
return p; return sctp_sched_prio_head_get(p);
} }
/* If not even there, allocate a new one. */ /* If not even there, allocate a new one. */
@ -154,32 +167,21 @@ static int sctp_sched_prio_set(struct sctp_stream *stream, __u16 sid,
struct sctp_stream_out_ext *soute = sout->ext; struct sctp_stream_out_ext *soute = sout->ext;
struct sctp_stream_priorities *prio_head, *old; struct sctp_stream_priorities *prio_head, *old;
bool reschedule = false; bool reschedule = false;
int i;
old = soute->prio_head;
if (old && old->prio == prio)
return 0;
prio_head = sctp_sched_prio_get_head(stream, prio, gfp); prio_head = sctp_sched_prio_get_head(stream, prio, gfp);
if (!prio_head) if (!prio_head)
return -ENOMEM; return -ENOMEM;
reschedule = sctp_sched_prio_unsched(soute); reschedule = sctp_sched_prio_unsched(soute);
old = soute->prio_head;
soute->prio_head = prio_head; soute->prio_head = prio_head;
if (reschedule) if (reschedule)
sctp_sched_prio_sched(stream, soute); sctp_sched_prio_sched(stream, soute);
if (!old) sctp_sched_prio_head_put(old);
/* Happens when we set the priority for the first time */
return 0;
for (i = 0; i < stream->outcnt; i++) {
soute = SCTP_SO(stream, i)->ext;
if (soute && soute->prio_head == old)
/* It's still in use, nothing else to do here. */
return 0;
}
/* No hits, we are good to free it. */
kfree(old);
return 0; return 0;
} }
@ -206,20 +208,8 @@ static int sctp_sched_prio_init_sid(struct sctp_stream *stream, __u16 sid,
static void sctp_sched_prio_free_sid(struct sctp_stream *stream, __u16 sid) static void sctp_sched_prio_free_sid(struct sctp_stream *stream, __u16 sid)
{ {
struct sctp_stream_priorities *prio = SCTP_SO(stream, sid)->ext->prio_head; sctp_sched_prio_head_put(SCTP_SO(stream, sid)->ext->prio_head);
int i;
if (!prio)
return;
SCTP_SO(stream, sid)->ext->prio_head = NULL; SCTP_SO(stream, sid)->ext->prio_head = NULL;
for (i = 0; i < stream->outcnt; i++) {
if (SCTP_SO(stream, i)->ext &&
SCTP_SO(stream, i)->ext->prio_head == prio)
return;
}
kfree(prio);
} }
static void sctp_sched_prio_free(struct sctp_stream *stream) static void sctp_sched_prio_free(struct sctp_stream *stream)