Skip to content

Commit 90f33bf

Browse files
Nikolay Aleksandrovdavem330
authored andcommitted
nexthops: don't modify published nexthop groups
We must avoid modifying published nexthop groups while they might be in use, otherwise we might see NULL ptr dereferences. In order to do that we allocate 2 nexthoup group structures upon nexthop creation and swap between them when we have to delete an entry. The reason is that we can't fail nexthop group removal, so we can't handle allocation failure thus we move the extra allocation on creation where we can safely fail and return ENOMEM. Fixes: 430a049 ("nexthop: Add support for nexthop groups") Signed-off-by: Nikolay Aleksandrov <[email protected]> Signed-off-by: David Ahern <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent ac21753 commit 90f33bf

File tree

2 files changed

+59
-33
lines changed

2 files changed

+59
-33
lines changed

include/net/nexthop.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ struct nh_grp_entry {
7070
};
7171

7272
struct nh_group {
73+
struct nh_group *spare; /* spare group for removals */
7374
u16 num_nh;
7475
bool mpath;
7576
bool has_v4;

net/ipv4/nexthop.c

Lines changed: 58 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,16 @@ static void nexthop_free_mpath(struct nexthop *nh)
6363
int i;
6464

6565
nhg = rcu_dereference_raw(nh->nh_grp);
66-
for (i = 0; i < nhg->num_nh; ++i)
67-
WARN_ON(nhg->nh_entries[i].nh);
66+
for (i = 0; i < nhg->num_nh; ++i) {
67+
struct nh_grp_entry *nhge = &nhg->nh_entries[i];
6868

69+
WARN_ON(!list_empty(&nhge->nh_list));
70+
nexthop_put(nhge->nh);
71+
}
72+
73+
WARN_ON(nhg->spare == nhg);
74+
75+
kfree(nhg->spare);
6976
kfree(nhg);
7077
}
7178

@@ -697,46 +704,53 @@ static void nh_group_rebalance(struct nh_group *nhg)
697704
static void remove_nh_grp_entry(struct net *net, struct nh_grp_entry *nhge,
698705
struct nl_info *nlinfo)
699706
{
707+
struct nh_grp_entry *nhges, *new_nhges;
700708
struct nexthop *nhp = nhge->nh_parent;
701709
struct nexthop *nh = nhge->nh;
702-
struct nh_grp_entry *nhges;
703-
struct nh_group *nhg;
704-
bool found = false;
705-
int i;
710+
struct nh_group *nhg, *newg;
711+
int i, j;
706712

707713
WARN_ON(!nh);
708714

709-
list_del(&nhge->nh_list);
710-
711715
nhg = rtnl_dereference(nhp->nh_grp);
712-
nhges = nhg->nh_entries;
713-
for (i = 0; i < nhg->num_nh; ++i) {
714-
if (found) {
715-
nhges[i-1].nh = nhges[i].nh;
716-
nhges[i-1].weight = nhges[i].weight;
717-
list_del(&nhges[i].nh_list);
718-
list_add(&nhges[i-1].nh_list, &nhges[i-1].nh->grp_list);
719-
} else if (nhg->nh_entries[i].nh == nh) {
720-
found = true;
721-
}
722-
}
716+
newg = nhg->spare;
723717

724-
if (WARN_ON(!found))
718+
/* last entry, keep it visible and remove the parent */
719+
if (nhg->num_nh == 1) {
720+
remove_nexthop(net, nhp, nlinfo);
725721
return;
722+
}
726723

727-
nhg->num_nh--;
728-
nhg->nh_entries[nhg->num_nh].nh = NULL;
724+
newg->has_v4 = nhg->has_v4;
725+
newg->mpath = nhg->mpath;
726+
newg->num_nh = nhg->num_nh;
729727

730-
nh_group_rebalance(nhg);
728+
/* copy old entries to new except the one getting removed */
729+
nhges = nhg->nh_entries;
730+
new_nhges = newg->nh_entries;
731+
for (i = 0, j = 0; i < nhg->num_nh; ++i) {
732+
/* current nexthop getting removed */
733+
if (nhg->nh_entries[i].nh == nh) {
734+
newg->num_nh--;
735+
continue;
736+
}
731737

732-
nexthop_put(nh);
738+
list_del(&nhges[i].nh_list);
739+
new_nhges[j].nh_parent = nhges[i].nh_parent;
740+
new_nhges[j].nh = nhges[i].nh;
741+
new_nhges[j].weight = nhges[i].weight;
742+
list_add(&new_nhges[j].nh_list, &new_nhges[j].nh->grp_list);
743+
j++;
744+
}
745+
746+
nh_group_rebalance(newg);
747+
rcu_assign_pointer(nhp->nh_grp, newg);
748+
749+
list_del(&nhge->nh_list);
750+
nexthop_put(nhge->nh);
733751

734752
if (nlinfo)
735753
nexthop_notify(RTM_NEWNEXTHOP, nhp, nlinfo);
736-
737-
/* if this group has no more entries then remove it */
738-
if (!nhg->num_nh)
739-
remove_nexthop(net, nhp, nlinfo);
740754
}
741755

742756
static void remove_nexthop_from_groups(struct net *net, struct nexthop *nh,
@@ -746,6 +760,9 @@ static void remove_nexthop_from_groups(struct net *net, struct nexthop *nh,
746760

747761
list_for_each_entry_safe(nhge, tmp, &nh->grp_list, nh_list)
748762
remove_nh_grp_entry(net, nhge, nlinfo);
763+
764+
/* make sure all see the newly published array before releasing rtnl */
765+
synchronize_rcu();
749766
}
750767

751768
static void remove_nexthop_group(struct nexthop *nh, struct nl_info *nlinfo)
@@ -759,10 +776,7 @@ static void remove_nexthop_group(struct nexthop *nh, struct nl_info *nlinfo)
759776
if (WARN_ON(!nhge->nh))
760777
continue;
761778

762-
list_del(&nhge->nh_list);
763-
nexthop_put(nhge->nh);
764-
nhge->nh = NULL;
765-
nhg->num_nh--;
779+
list_del_init(&nhge->nh_list);
766780
}
767781
}
768782

@@ -1085,6 +1099,7 @@ static struct nexthop *nexthop_create_group(struct net *net,
10851099
{
10861100
struct nlattr *grps_attr = cfg->nh_grp;
10871101
struct nexthop_grp *entry = nla_data(grps_attr);
1102+
u16 num_nh = nla_len(grps_attr) / sizeof(*entry);
10881103
struct nh_group *nhg;
10891104
struct nexthop *nh;
10901105
int i;
@@ -1095,12 +1110,21 @@ static struct nexthop *nexthop_create_group(struct net *net,
10951110

10961111
nh->is_group = 1;
10971112

1098-
nhg = nexthop_grp_alloc(nla_len(grps_attr) / sizeof(*entry));
1113+
nhg = nexthop_grp_alloc(num_nh);
10991114
if (!nhg) {
11001115
kfree(nh);
11011116
return ERR_PTR(-ENOMEM);
11021117
}
11031118

1119+
/* spare group used for removals */
1120+
nhg->spare = nexthop_grp_alloc(num_nh);
1121+
if (!nhg) {
1122+
kfree(nhg);
1123+
kfree(nh);
1124+
return NULL;
1125+
}
1126+
nhg->spare->spare = nhg;
1127+
11041128
for (i = 0; i < nhg->num_nh; ++i) {
11051129
struct nexthop *nhe;
11061130
struct nh_info *nhi;
@@ -1132,6 +1156,7 @@ static struct nexthop *nexthop_create_group(struct net *net,
11321156
for (; i >= 0; --i)
11331157
nexthop_put(nhg->nh_entries[i].nh);
11341158

1159+
kfree(nhg->spare);
11351160
kfree(nhg);
11361161
kfree(nh);
11371162

0 commit comments

Comments
 (0)