@@ -37,23 +37,28 @@
* @ts_needaddr - Need to record addr of outgoing dev
*/
struct ip_options {
- __be32 faddr;
- __be32 nexthop;
- unsigned char optlen;
- unsigned char srr;
- unsigned char rr;
- unsigned char ts;
- unsigned char is_strictroute:1,
- srr_is_hit:1,
- is_changed:1,
- rr_needaddr:1,
- ts_needtime:1,
- ts_needaddr:1;
- unsigned char router_alert;
- unsigned char cipso;
- unsigned char __pad2;
+ /* New members MUST be added within the struct_group() macro below. */
+ struct_group_tagged(ip_options_hdr, __hdr,
+ __be32 faddr;
+ __be32 nexthop;
+ unsigned char optlen;
+ unsigned char srr;
+ unsigned char rr;
+ unsigned char ts;
+ unsigned char is_strictroute:1,
+ srr_is_hit:1,
+ is_changed:1,
+ rr_needaddr:1,
+ ts_needtime:1,
+ ts_needaddr:1;
+ unsigned char router_alert;
+ unsigned char cipso;
+ unsigned char __pad2;
+ );
unsigned char __data[];
};
+static_assert(offsetof(struct ip_options, __data) == sizeof(struct ip_options_hdr),
+ "struct member likely outside of struct_group_tagged()");
struct ip_options_rcu {
struct rcu_head rcu;
@@ -61,7 +66,10 @@ struct ip_options_rcu {
};
struct ip_options_data {
- struct ip_options_rcu opt;
+ struct {
+ struct rcu_head rcu;
+ struct ip_options_hdr opt;
+ } opt;
char data[40];
};
@@ -400,6 +400,9 @@ static void icmp_push_reply(struct sock *sk,
static void icmp_reply(struct icmp_bxm *icmp_param, struct sk_buff *skb)
{
+ struct ip_options *opt =
+ container_of(&icmp_param->replyopts.opt.opt,
+ struct ip_options, __hdr);
struct ipcm_cookie ipc;
struct rtable *rt = skb_rtable(skb);
struct net *net = dev_net(rt->dst.dev);
@@ -412,7 +415,7 @@ static void icmp_reply(struct icmp_bxm *icmp_param, struct sk_buff *skb)
int type = icmp_param->data.icmph.type;
int code = icmp_param->data.icmph.code;
- if (ip_options_echo(net, &icmp_param->replyopts.opt.opt, skb))
+ if (ip_options_echo(net, opt, skb))
return;
/* Needed by both icmpv4_global_allow and icmp_xmit_lock */
@@ -436,7 +439,7 @@ static void icmp_reply(struct icmp_bxm *icmp_param, struct sk_buff *skb)
saddr = fib_compute_spec_dst(skb);
if (icmp_param->replyopts.opt.opt.optlen) {
- ipc.opt = &icmp_param->replyopts.opt;
+ ipc.opt = (struct ip_options_rcu *)&icmp_param->replyopts.opt;
if (ipc.opt->opt.srr)
daddr = icmp_param->replyopts.opt.opt.faddr;
}
@@ -592,6 +595,7 @@ static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4,
void __icmp_send(struct sk_buff *skb_in, int type, int code, __be32 info,
const struct ip_options *opt)
{
+ struct ip_options *opt_aux;
struct iphdr *iph;
int room;
struct icmp_bxm icmp_param;
@@ -605,6 +609,8 @@ void __icmp_send(struct sk_buff *skb_in, int type, int code, __be32 info,
struct net *net;
struct sock *sk;
+ opt_aux = container_of(&icmp_param.replyopts.opt.opt, struct ip_options, __hdr);
+
if (!rt)
goto out;
@@ -719,7 +725,7 @@ void __icmp_send(struct sk_buff *skb_in, int type, int code, __be32 info,
iph->tos;
mark = IP4_REPLY_MARK(net, skb_in->mark);
- if (__ip_options_echo(net, &icmp_param.replyopts.opt.opt, skb_in, opt))
+ if (__ip_options_echo(net, opt_aux, skb_in, opt))
goto out_unlock;
@@ -736,7 +742,7 @@ void __icmp_send(struct sk_buff *skb_in, int type, int code, __be32 info,
inet_sk(sk)->tos = tos;
ipcm_init(&ipc);
ipc.addr = iph->saddr;
- ipc.opt = &icmp_param.replyopts.opt;
+ ipc.opt = (struct ip_options_rcu *)&icmp_param.replyopts.opt;
ipc.sockc.mark = mark;
rt = icmp_route_lookup(net, &fl4, skb_in, iph, saddr,
@@ -1604,6 +1604,7 @@ void ip_send_unicast_reply(struct sock *sk, const struct sock *orig_sk,
unsigned int len, u64 transmit_time, u32 txhash)
{
struct ip_options_data replyopts;
+ struct ip_options *opt;
struct ipcm_cookie ipc;
struct flowi4 fl4;
struct rtable *rt = skb_rtable(skb);
@@ -1612,7 +1613,9 @@ void ip_send_unicast_reply(struct sock *sk, const struct sock *orig_sk,
int err;
int oif;
- if (__ip_options_echo(net, &replyopts.opt.opt, skb, sopt))
+ opt = container_of(&replyopts.opt.opt, struct ip_options, __hdr);
+
+ if (__ip_options_echo(net, opt, skb, sopt))
return;
ipcm_init(&ipc);
@@ -1620,7 +1623,7 @@ void ip_send_unicast_reply(struct sock *sk, const struct sock *orig_sk,
ipc.sockc.transmit_time = transmit_time;
if (replyopts.opt.opt.optlen) {
- ipc.opt = &replyopts.opt;
+ ipc.opt = (struct ip_options_rcu *)&replyopts.opt;
if (replyopts.opt.opt.srr)
daddr = replyopts.opt.opt.faddr;
@@ -753,7 +753,7 @@ static int ping_v4_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
if (inet_opt) {
memcpy(&opt_copy, inet_opt,
sizeof(*inet_opt) + inet_opt->opt.optlen);
- ipc.opt = &opt_copy.opt;
+ ipc.opt = (struct ip_options_rcu *)&opt_copy.opt;
}
rcu_read_unlock();
}
@@ -563,7 +563,7 @@ static int raw_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
if (inet_opt) {
memcpy(&opt_copy, inet_opt,
sizeof(*inet_opt) + inet_opt->opt.optlen);
- ipc.opt = &opt_copy.opt;
+ ipc.opt = (struct ip_options_rcu *)&opt_copy.opt;
}
rcu_read_unlock();
}
@@ -1159,7 +1159,7 @@ int udp_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
if (inet_opt) {
memcpy(&opt_copy, inet_opt,
sizeof(*inet_opt) + inet_opt->opt.optlen);
- ipc.opt = &opt_copy.opt;
+ ipc.opt = (struct ip_options_rcu *)&opt_copy.opt;
}
rcu_read_unlock();
}
-Wflex-array-member-not-at-end was introduced in GCC-14, and we are getting ready to enable it, globally So, in order to avoid ending up with a flexible-array member in the middle of another struct, we use the `struct_group_tagged()` helper to create a new tagged `struct ip_options_hdr`. This structure groups together all the members of the flexible `struct ip_options` except the flexible array. As a result, the array is effectively separated from the rest of the members without modifying the memory layout of the flexible structure. However, the pattern that triggers the warnings addressed by this patch differs from the usual case. Typically, the flexible structure is immediately followed by another object in the composite structure. Here, however, we have a well-defined composite structure with a flexible structure right at its end, which is a correct code construct and has no issues on its own. An instance of this composite structure (`struct ip_options_rcu`) is then used within another structure (`struct ip_options_data`) right before a fixed-size array (`char data[40]`), as below: /* Modified flexible structure with struct_group_tagged() */ struct ip_options { struct_group_tagged(ip_options_hdr, __hdr, ... ); unsigned char __data[]; }; /* Correct composite struct with flex struct at the end. */ struct ip_options_rcu { struct rcu_head rcu; struct ip_options opt; /* flexible structure --this is fine. */ }; struct ip_options_data { struct ip_options_rcu opt; /* Conflicting object causing tons * of -Wfamnae warnings --it ends in * flexible-array member `__data`. */ char data[40]; }; In this scenario, we have two levels of indirection before reaching the flexible array: `p->opt->opt->__data`. As a result, we can't simply change the type of the conflicting object `opt` to `struct ip_options_hdr` as we might in a simpler case, because `opt` in `struct ip_options_data` isn't a flexible structure itself but a composite structure `struct ip_options_rcu` containing one (flexible structure). So, we have a couple of alternatives to fix this issue: a) The complex solution: Create an additional "header type" with the help of `struct_group_tagged()`, and introduce another flexible-array member, as follows: 1) Create a new type `struct ip_options_rcu_hdr` using `struct_group_tagged()` within `struct ip_options_rcu`. 2) Change the type of `opt` from `struct ip_options` to `struct ip_options_hdr`, removing the flexible array member from the newly created `struct ip_options_rcu_hdr`. 3) Add a flexible-array member directly inside `struct ip_options_rcu`, so it still has a flexible-array member at the end --which was previously "removed" with the change from `struct ip_options opt;` to `struct ip_options_hdr opt;`: | struct ip_options_rcu { | - struct rcu_head rcu; | - struct ip_options opt; | + struct_group_tagged(ip_options_rcu_hdr, hdr, | + struct rcu_head rcu; | + struct ip_options_hdr opt; | + ); | + unsigned char __data[]; | }; 4) Now we can change the type of the conflicting object `opt` in `struct ip_options_data` from `struct ip_options_rcu` to `struct ip_options_rcu_hdr`, and with this silence the -Wfamnae warnings: | struct ip_options_data { | - struct ip_options_rcu opt; | - char data[40]; | + struct ip_options_rcu_hdr opt; | + char data[40]; | }; 5) Code churn: This approach may (it really does) require more adjustments and refactoring than usual due to multiple type changes and the extra flexible-array member. b) A simpler solution: Inline `struct ip_options_rcu` directly into `struct ip_options_data` using an anonymous structure. See below: 1) Expand `struct ip_options_rcu` with an anonymous structure named `opt`, preserving access to `struct ip_options_rcu` members via `opt`. 2) Change the type of `opt` in the anonymous structure from `struct ip_options` to `struct ip_options_hdr`, excluding the flexible array and thus making the warnings go away: | struct ip_options_data { | - struct ip_options_rcu opt; | + struct { | + struct rcu_head rcu; | + struct ip_options_hdr opt; | + } opt; | char data[40]; | }; 3) Code churn: This solution requires minimal refactoring beyond type casting instances of `opt` back to its original type `struct ip_options_rcu`, and a couple of `container_of()`s. So, I've decided to implement solution b) and with this, fix 2554 of the following warnings: include/net/inet_sock.h:64:33: warning: structure containing a flexible array member is not at the end of another structure [-Wflex-array-member-not-at-end] Additionally, we want to ensure that when new members need to be added to the flexible structure, they are always included within the newly created tagged struct. For this, we use `static_assert()`. This ensures that the memory layout for both the flexible structure and the new tagged struct is the same after any changes. Signed-off-by: Gustavo A. R. Silva <gustavoars@kernel.org> --- include/net/inet_sock.h | 40 ++++++++++++++++++++++++---------------- net/ipv4/icmp.c | 14 ++++++++++---- net/ipv4/ip_output.c | 7 +++++-- net/ipv4/ping.c | 2 +- net/ipv4/raw.c | 2 +- net/ipv4/udp.c | 2 +- 6 files changed, 42 insertions(+), 25 deletions(-)