All the mail mirrored from lore.kernel.org
 help / color / mirror / Atom feed
From: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
To: Oliver Hartkopp <socketcan@hartkopp.net>
Cc: linux-can@vger.kernel.org, tglx@linutronix.de
Subject: Re: [RFC PATCH] can: gw: fix RCU/BH usage in cgw_create_job()
Date: Thu, 11 Jan 2024 13:14:02 +0100	[thread overview]
Message-ID: <20240111121402.xc9rmYfG@linutronix.de> (raw)
In-Reply-To: <20231221123703.8170-1-socketcan@hartkopp.net>

On 2023-12-21 13:37:03 [+0100], Oliver Hartkopp wrote:
> --- a/net/can/gw.c
> +++ b/net/can/gw.c
> @@ -128,11 +128,13 @@ struct cgw_job {
>  	struct hlist_node list;
>  	struct rcu_head rcu;
>  	u32 handled_frames;
>  	u32 dropped_frames;
>  	u32 deleted_frames;
> -	struct cf_mod mod;
> +	struct cf_mod *mod;
> +	struct cf_mod mod1;
> +	struct cf_mod mod2;

I wouldn't put here mod1/2. That cf_mod struct has 736 bytes in my
allmod build so it gains some weight. It also introduces a run-time
problem, more on that later.
Also *mod requires __rcu annotation. 

> @@ -504,11 +506,11 @@ static void can_can_gw_rcv(struct sk_buff *skb, void *data)
>  	/* clone the given skb, which has not been done in can_rcv()
>  	 *
>  	 * When there is at least one modification function activated,
>  	 * we need to copy the skb as we want to modify skb->data.
>  	 */
> -	if (gwj->mod.modfunc[0])
> +	if (gwj->mod->modfunc[0])

Almost. You need
	struct cf_mod *mod;
	…

	rcu_read_lock();
	mod = rcu_dereference(gwj->mod);
	if (mod->modfunc[0])
	…

	rcu_read_unlock();
	/* no longer touching mod but I am leaving can_can_gw_rcv()
	 * anyway */

>  		nskb = skb_copy(skb, GFP_ATOMIC);
>  	else
>  		nskb = skb_clone(skb, GFP_ATOMIC);
>  
>  	if (!nskb) {
> @@ -1085,21 +1087,25 @@ static int cgw_create_job(struct sk_buff *skb,  struct nlmsghdr *nlh,
>  	if (mod.uid) {
>  		ASSERT_RTNL();
>  
>  		/* check for updating an existing job with identical uid */
>  		hlist_for_each_entry(gwj, &net->can.cgw_list, list) {
> -			if (gwj->mod.uid != mod.uid)
> +			if (gwj->mod->uid != mod.uid)
>  				continue;
>  
>  			/* interfaces & filters must be identical */
>  			if (memcmp(&gwj->ccgw, &ccgw, sizeof(ccgw)))
>  				return -EINVAL;
>  
>  			/* update modifications with disabled softirq & quit */
> -			local_bh_disable();
> -			memcpy(&gwj->mod, &mod, sizeof(mod));
> -			local_bh_enable();
> +			if (gwj->mod == &gwj->mod1) {
> +				memcpy(&gwj->mod2, &mod, sizeof(mod));
> +				rcu_replace_pointer(gwj->mod, &gwj->mod2, true);
> +			} else {
> +				memcpy(&gwj->mod1, &mod, sizeof(mod));
> +				rcu_replace_pointer(gwj->mod, &gwj->mod1, true);
> +			}

Why are you afraid of doing
	mod = kmalloc(sizeof(*mod), GFP_KERNEL);

before invoking cgw_parse_attr()?

Let me try to sell this to you:
- Your stack usage shrinks by 736 bytes (as per previous estimation)
- If this is a new one, you attach the pointer to the struct cgw_job so
  it is not lost.
- If this is an update, you avoid memcpy() and use rcu_replace_pointer()
  as it is intended.

The construct of yours has the problem that it does not wait until the
RCU grace period completes. Meaning on first update mod1 is used, you
switch over to mod2. On the second update mod2 is used and you switch
back to mod1. There is no guarantee that all current users of mod ->
mod1 are gone by the time you switch from mod2 back to mod1. This
of course requires two updates (one after the other).

If you allocate the struct on entry (assuming the salesmen me was
successful) you could use 

	old_mod = rcu_replace_pointer(gwj->mod, new_mod, lockdep_rtnl_is_held());

to grab the about to be replaced cf_mod and once the replacement is
over and at the end, kfree_rcu() on it or
	kfree_rcu_mightsleep(old_mod);

to safe a RCU head.

>  			return 0;
>  		}
>  	}
>  
>  	/* ifindex == 0 is not allowed for job creation */
> @@ -1219,16 +1226,16 @@ static int cgw_remove_job(struct sk_buff *skb, struct nlmsghdr *nlh,
>  
>  		if (gwj->limit_hops != limhops)
>  			continue;
>  
>  		/* we have a match when uid is enabled and identical */
> -		if (gwj->mod.uid || mod.uid) {
> -			if (gwj->mod.uid != mod.uid)
> +		if (gwj->mod->uid || mod.uid) {
> +			if (gwj->mod->uid != mod.uid)
>  				continue;

Also here you must use rcu_dereference() for gwj->mod. Since all
add/remove jobs happen under the RTNL lock you could use
	rcu_dereference_protected(, lockdep_rtnl_is_held());

Sebastian

  reply	other threads:[~2024-01-11 12:14 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-12-21 12:37 [RFC PATCH] can: gw: fix RCU/BH usage in cgw_create_job() Oliver Hartkopp
2024-01-11 12:14 ` Sebastian Andrzej Siewior [this message]
2024-01-21 10:07   ` Oliver Hartkopp
2024-01-22 10:10     ` Sebastian Andrzej Siewior
2024-04-29  9:47       ` Sebastian Andrzej Siewior

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20240111121402.xc9rmYfG@linutronix.de \
    --to=bigeasy@linutronix.de \
    --cc=linux-can@vger.kernel.org \
    --cc=socketcan@hartkopp.net \
    --cc=tglx@linutronix.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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.