Skip to content

Commit 26dfae3

Browse files
hartkoppNipaLocal
authored and
NipaLocal
committed
can: gw: fix RCU/BH usage in cgw_create_job()
As reported by Sebastian Andrzej Siewior the use of local_bh_disable() is only feasible in uni processor systems to update the modification rules. The usual use-case to update the modification rules is to update the data of the modifications but not the modification types (AND/OR/XOR/SET) or the checksum functions itself. To omit additional memory allocations to maintain fast modification switching times, the modification description space is doubled at gw-job creation time so that only the reference to the active modification description is changed under rcu protection. Rename cgw_job::mod to cf_mod and make it a RCU pointer. Allocate in cgw_create_job() and free it together with cgw_job in cgw_job_free_rcu(). Update all users to dereference cgw_job::cf_mod with a RCU accessor and if possible once. [bigeasy: Replace mod1/mod2 from the Oliver's original patch with dynamic allocation, use RCU annotation and accessor] Reported-by: Sebastian Andrzej Siewior <[email protected]> Closes: https://lore.kernel.org/linux-can/[email protected]/ Fixes: dd895d7 ("can: cangw: introduce optional uid to reference created routing jobs") Tested-by: Oliver Hartkopp <[email protected]> Signed-off-by: Oliver Hartkopp <[email protected]> Signed-off-by: Sebastian Andrzej Siewior <[email protected]> Link: https://patch.msgid.link/[email protected] Signed-off-by: Marc Kleine-Budde <[email protected]> Reviewed-by: Jacob Keller <[email protected]> Signed-off-by: NipaLocal <nipa@local>
1 parent 6856d07 commit 26dfae3

File tree

1 file changed

+90
-59
lines changed

1 file changed

+90
-59
lines changed

net/can/gw.c

Lines changed: 90 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ struct cgw_job {
130130
u32 handled_frames;
131131
u32 dropped_frames;
132132
u32 deleted_frames;
133-
struct cf_mod mod;
133+
struct cf_mod __rcu *cf_mod;
134134
union {
135135
/* CAN frame data source */
136136
struct net_device *dev;
@@ -459,6 +459,7 @@ static void can_can_gw_rcv(struct sk_buff *skb, void *data)
459459
struct cgw_job *gwj = (struct cgw_job *)data;
460460
struct canfd_frame *cf;
461461
struct sk_buff *nskb;
462+
struct cf_mod *mod;
462463
int modidx = 0;
463464

464465
/* process strictly Classic CAN or CAN FD frames */
@@ -506,7 +507,8 @@ static void can_can_gw_rcv(struct sk_buff *skb, void *data)
506507
* When there is at least one modification function activated,
507508
* we need to copy the skb as we want to modify skb->data.
508509
*/
509-
if (gwj->mod.modfunc[0])
510+
mod = rcu_dereference(gwj->cf_mod);
511+
if (mod->modfunc[0])
510512
nskb = skb_copy(skb, GFP_ATOMIC);
511513
else
512514
nskb = skb_clone(skb, GFP_ATOMIC);
@@ -529,8 +531,8 @@ static void can_can_gw_rcv(struct sk_buff *skb, void *data)
529531
cf = (struct canfd_frame *)nskb->data;
530532

531533
/* perform preprocessed modification functions if there are any */
532-
while (modidx < MAX_MODFUNCTIONS && gwj->mod.modfunc[modidx])
533-
(*gwj->mod.modfunc[modidx++])(cf, &gwj->mod);
534+
while (modidx < MAX_MODFUNCTIONS && mod->modfunc[modidx])
535+
(*mod->modfunc[modidx++])(cf, mod);
534536

535537
/* Has the CAN frame been modified? */
536538
if (modidx) {
@@ -546,11 +548,11 @@ static void can_can_gw_rcv(struct sk_buff *skb, void *data)
546548
}
547549

548550
/* check for checksum updates */
549-
if (gwj->mod.csumfunc.crc8)
550-
(*gwj->mod.csumfunc.crc8)(cf, &gwj->mod.csum.crc8);
551+
if (mod->csumfunc.crc8)
552+
(*mod->csumfunc.crc8)(cf, &mod->csum.crc8);
551553

552-
if (gwj->mod.csumfunc.xor)
553-
(*gwj->mod.csumfunc.xor)(cf, &gwj->mod.csum.xor);
554+
if (mod->csumfunc.xor)
555+
(*mod->csumfunc.xor)(cf, &mod->csum.xor);
554556
}
555557

556558
/* clear the skb timestamp if not configured the other way */
@@ -581,9 +583,20 @@ static void cgw_job_free_rcu(struct rcu_head *rcu_head)
581583
{
582584
struct cgw_job *gwj = container_of(rcu_head, struct cgw_job, rcu);
583585

586+
/* cgw_job::cf_mod is always accessed from the same cgw_job object within
587+
* the same RCU read section. Once cgw_job is scheduled for removal,
588+
* cf_mod can also be removed without mandating an additional grace period.
589+
*/
590+
kfree(rcu_access_pointer(gwj->cf_mod));
584591
kmem_cache_free(cgw_cache, gwj);
585592
}
586593

594+
/* Return cgw_job::cf_mod with RTNL protected section */
595+
static struct cf_mod *cgw_job_cf_mod(struct cgw_job *gwj)
596+
{
597+
return rcu_dereference_protected(gwj->cf_mod, rtnl_is_locked());
598+
}
599+
587600
static int cgw_notifier(struct notifier_block *nb,
588601
unsigned long msg, void *ptr)
589602
{
@@ -616,6 +629,7 @@ static int cgw_put_job(struct sk_buff *skb, struct cgw_job *gwj, int type,
616629
{
617630
struct rtcanmsg *rtcan;
618631
struct nlmsghdr *nlh;
632+
struct cf_mod *mod;
619633

620634
nlh = nlmsg_put(skb, pid, seq, type, sizeof(*rtcan), flags);
621635
if (!nlh)
@@ -650,82 +664,83 @@ static int cgw_put_job(struct sk_buff *skb, struct cgw_job *gwj, int type,
650664
goto cancel;
651665
}
652666

667+
mod = cgw_job_cf_mod(gwj);
653668
if (gwj->flags & CGW_FLAGS_CAN_FD) {
654669
struct cgw_fdframe_mod mb;
655670

656-
if (gwj->mod.modtype.and) {
657-
memcpy(&mb.cf, &gwj->mod.modframe.and, sizeof(mb.cf));
658-
mb.modtype = gwj->mod.modtype.and;
671+
if (mod->modtype.and) {
672+
memcpy(&mb.cf, &mod->modframe.and, sizeof(mb.cf));
673+
mb.modtype = mod->modtype.and;
659674
if (nla_put(skb, CGW_FDMOD_AND, sizeof(mb), &mb) < 0)
660675
goto cancel;
661676
}
662677

663-
if (gwj->mod.modtype.or) {
664-
memcpy(&mb.cf, &gwj->mod.modframe.or, sizeof(mb.cf));
665-
mb.modtype = gwj->mod.modtype.or;
678+
if (mod->modtype.or) {
679+
memcpy(&mb.cf, &mod->modframe.or, sizeof(mb.cf));
680+
mb.modtype = mod->modtype.or;
666681
if (nla_put(skb, CGW_FDMOD_OR, sizeof(mb), &mb) < 0)
667682
goto cancel;
668683
}
669684

670-
if (gwj->mod.modtype.xor) {
671-
memcpy(&mb.cf, &gwj->mod.modframe.xor, sizeof(mb.cf));
672-
mb.modtype = gwj->mod.modtype.xor;
685+
if (mod->modtype.xor) {
686+
memcpy(&mb.cf, &mod->modframe.xor, sizeof(mb.cf));
687+
mb.modtype = mod->modtype.xor;
673688
if (nla_put(skb, CGW_FDMOD_XOR, sizeof(mb), &mb) < 0)
674689
goto cancel;
675690
}
676691

677-
if (gwj->mod.modtype.set) {
678-
memcpy(&mb.cf, &gwj->mod.modframe.set, sizeof(mb.cf));
679-
mb.modtype = gwj->mod.modtype.set;
692+
if (mod->modtype.set) {
693+
memcpy(&mb.cf, &mod->modframe.set, sizeof(mb.cf));
694+
mb.modtype = mod->modtype.set;
680695
if (nla_put(skb, CGW_FDMOD_SET, sizeof(mb), &mb) < 0)
681696
goto cancel;
682697
}
683698
} else {
684699
struct cgw_frame_mod mb;
685700

686-
if (gwj->mod.modtype.and) {
687-
memcpy(&mb.cf, &gwj->mod.modframe.and, sizeof(mb.cf));
688-
mb.modtype = gwj->mod.modtype.and;
701+
if (mod->modtype.and) {
702+
memcpy(&mb.cf, &mod->modframe.and, sizeof(mb.cf));
703+
mb.modtype = mod->modtype.and;
689704
if (nla_put(skb, CGW_MOD_AND, sizeof(mb), &mb) < 0)
690705
goto cancel;
691706
}
692707

693-
if (gwj->mod.modtype.or) {
694-
memcpy(&mb.cf, &gwj->mod.modframe.or, sizeof(mb.cf));
695-
mb.modtype = gwj->mod.modtype.or;
708+
if (mod->modtype.or) {
709+
memcpy(&mb.cf, &mod->modframe.or, sizeof(mb.cf));
710+
mb.modtype = mod->modtype.or;
696711
if (nla_put(skb, CGW_MOD_OR, sizeof(mb), &mb) < 0)
697712
goto cancel;
698713
}
699714

700-
if (gwj->mod.modtype.xor) {
701-
memcpy(&mb.cf, &gwj->mod.modframe.xor, sizeof(mb.cf));
702-
mb.modtype = gwj->mod.modtype.xor;
715+
if (mod->modtype.xor) {
716+
memcpy(&mb.cf, &mod->modframe.xor, sizeof(mb.cf));
717+
mb.modtype = mod->modtype.xor;
703718
if (nla_put(skb, CGW_MOD_XOR, sizeof(mb), &mb) < 0)
704719
goto cancel;
705720
}
706721

707-
if (gwj->mod.modtype.set) {
708-
memcpy(&mb.cf, &gwj->mod.modframe.set, sizeof(mb.cf));
709-
mb.modtype = gwj->mod.modtype.set;
722+
if (mod->modtype.set) {
723+
memcpy(&mb.cf, &mod->modframe.set, sizeof(mb.cf));
724+
mb.modtype = mod->modtype.set;
710725
if (nla_put(skb, CGW_MOD_SET, sizeof(mb), &mb) < 0)
711726
goto cancel;
712727
}
713728
}
714729

715-
if (gwj->mod.uid) {
716-
if (nla_put_u32(skb, CGW_MOD_UID, gwj->mod.uid) < 0)
730+
if (mod->uid) {
731+
if (nla_put_u32(skb, CGW_MOD_UID, mod->uid) < 0)
717732
goto cancel;
718733
}
719734

720-
if (gwj->mod.csumfunc.crc8) {
735+
if (mod->csumfunc.crc8) {
721736
if (nla_put(skb, CGW_CS_CRC8, CGW_CS_CRC8_LEN,
722-
&gwj->mod.csum.crc8) < 0)
737+
&mod->csum.crc8) < 0)
723738
goto cancel;
724739
}
725740

726-
if (gwj->mod.csumfunc.xor) {
741+
if (mod->csumfunc.xor) {
727742
if (nla_put(skb, CGW_CS_XOR, CGW_CS_XOR_LEN,
728-
&gwj->mod.csum.xor) < 0)
743+
&mod->csum.xor) < 0)
729744
goto cancel;
730745
}
731746

@@ -1059,7 +1074,7 @@ static int cgw_create_job(struct sk_buff *skb, struct nlmsghdr *nlh,
10591074
struct net *net = sock_net(skb->sk);
10601075
struct rtcanmsg *r;
10611076
struct cgw_job *gwj;
1062-
struct cf_mod mod;
1077+
struct cf_mod *mod;
10631078
struct can_can_gw ccgw;
10641079
u8 limhops = 0;
10651080
int err = 0;
@@ -1078,37 +1093,48 @@ static int cgw_create_job(struct sk_buff *skb, struct nlmsghdr *nlh,
10781093
if (r->gwtype != CGW_TYPE_CAN_CAN)
10791094
return -EINVAL;
10801095

1081-
err = cgw_parse_attr(nlh, &mod, CGW_TYPE_CAN_CAN, &ccgw, &limhops);
1096+
mod = kmalloc(sizeof(*mod), GFP_KERNEL);
1097+
if (!mod)
1098+
return -ENOMEM;
1099+
1100+
err = cgw_parse_attr(nlh, mod, CGW_TYPE_CAN_CAN, &ccgw, &limhops);
10821101
if (err < 0)
1083-
return err;
1102+
goto out_free_cf;
10841103

1085-
if (mod.uid) {
1104+
if (mod->uid) {
10861105
ASSERT_RTNL();
10871106

10881107
/* check for updating an existing job with identical uid */
10891108
hlist_for_each_entry(gwj, &net->can.cgw_list, list) {
1090-
if (gwj->mod.uid != mod.uid)
1109+
struct cf_mod *old_cf;
1110+
1111+
old_cf = cgw_job_cf_mod(gwj);
1112+
if (old_cf->uid != mod->uid)
10911113
continue;
10921114

10931115
/* interfaces & filters must be identical */
1094-
if (memcmp(&gwj->ccgw, &ccgw, sizeof(ccgw)))
1095-
return -EINVAL;
1116+
if (memcmp(&gwj->ccgw, &ccgw, sizeof(ccgw))) {
1117+
err = -EINVAL;
1118+
goto out_free_cf;
1119+
}
10961120

1097-
/* update modifications with disabled softirq & quit */
1098-
local_bh_disable();
1099-
memcpy(&gwj->mod, &mod, sizeof(mod));
1100-
local_bh_enable();
1121+
rcu_assign_pointer(gwj->cf_mod, mod);
1122+
kfree_rcu_mightsleep(old_cf);
11011123
return 0;
11021124
}
11031125
}
11041126

11051127
/* ifindex == 0 is not allowed for job creation */
1106-
if (!ccgw.src_idx || !ccgw.dst_idx)
1107-
return -ENODEV;
1128+
if (!ccgw.src_idx || !ccgw.dst_idx) {
1129+
err = -ENODEV;
1130+
goto out_free_cf;
1131+
}
11081132

11091133
gwj = kmem_cache_alloc(cgw_cache, GFP_KERNEL);
1110-
if (!gwj)
1111-
return -ENOMEM;
1134+
if (!gwj) {
1135+
err = -ENOMEM;
1136+
goto out_free_cf;
1137+
}
11121138

11131139
gwj->handled_frames = 0;
11141140
gwj->dropped_frames = 0;
@@ -1118,7 +1144,7 @@ static int cgw_create_job(struct sk_buff *skb, struct nlmsghdr *nlh,
11181144
gwj->limit_hops = limhops;
11191145

11201146
/* insert already parsed information */
1121-
memcpy(&gwj->mod, &mod, sizeof(mod));
1147+
RCU_INIT_POINTER(gwj->cf_mod, mod);
11221148
memcpy(&gwj->ccgw, &ccgw, sizeof(ccgw));
11231149

11241150
err = -ENODEV;
@@ -1152,9 +1178,11 @@ static int cgw_create_job(struct sk_buff *skb, struct nlmsghdr *nlh,
11521178
if (!err)
11531179
hlist_add_head_rcu(&gwj->list, &net->can.cgw_list);
11541180
out:
1155-
if (err)
1181+
if (err) {
11561182
kmem_cache_free(cgw_cache, gwj);
1157-
1183+
out_free_cf:
1184+
kfree(mod);
1185+
}
11581186
return err;
11591187
}
11601188

@@ -1214,19 +1242,22 @@ static int cgw_remove_job(struct sk_buff *skb, struct nlmsghdr *nlh,
12141242

12151243
/* remove only the first matching entry */
12161244
hlist_for_each_entry_safe(gwj, nx, &net->can.cgw_list, list) {
1245+
struct cf_mod *cf_mod;
1246+
12171247
if (gwj->flags != r->flags)
12181248
continue;
12191249

12201250
if (gwj->limit_hops != limhops)
12211251
continue;
12221252

1253+
cf_mod = cgw_job_cf_mod(gwj);
12231254
/* we have a match when uid is enabled and identical */
1224-
if (gwj->mod.uid || mod.uid) {
1225-
if (gwj->mod.uid != mod.uid)
1255+
if (cf_mod->uid || mod.uid) {
1256+
if (cf_mod->uid != mod.uid)
12261257
continue;
12271258
} else {
12281259
/* no uid => check for identical modifications */
1229-
if (memcmp(&gwj->mod, &mod, sizeof(mod)))
1260+
if (memcmp(cf_mod, &mod, sizeof(mod)))
12301261
continue;
12311262
}
12321263

0 commit comments

Comments
 (0)