* [PATCH 5.10.y] net/sched: fix pedit partial COW leading to page cache corruption
@ 2026-06-30 8:05 Dominique Martinet
2026-06-30 8:27 ` Dominique Martinet
0 siblings, 1 reply; 4+ messages in thread
From: Dominique Martinet @ 2026-06-30 8:05 UTC (permalink / raw)
To: dominique.martinet, stable
From: Rajat Gupta <rajat.gupta@oss.qualcomm.com>
[ Upstream commit 899ee91156e57784090c5565e4f31bd7dbffbc5a ]
tcf_pedit_act() computes the COW range for skb_ensure_writable()
once before the key loop using tcfp_off_max_hint, but the hint does
not account for the runtime header offset added by typed keys. This
can leave part of the write region un-COW'd.
Fix by moving skb_ensure_writable() inside the per-key loop where
the actual write offset is known, and add overflow checking on the
offset arithmetic. For negative offsets (e.g. Ethernet header edits
at ingress), use skb_cow() to COW the headroom instead. Guard
offset_valid() against INT_MIN, where negation is undefined.
Fixes: 8b796475fd78 ("net/sched: act_pedit: really ensure the skb is writable")
Reported-by: Yiming Qian <yimingqian591@gmail.com>
Reported-by: Keenan Dong <keenanat2000@gmail.com>
Reported-by: Han Guidong <2045gemini@gmail.com>
Reported-by: Zhang Cen <rollkingzzc@gmail.com>
Reviewed-by: Han Guidong <2045gemini@gmail.com>
Tested-by: Han Guidong <2045gemini@gmail.com>
Reviewed-by: Davide Caratti <dcaratti@redhat.com>
Tested-by: Davide Caratti <dcaratti@redhat.com>
Reviewed-by: Toke Høiland-Jørgensen <toke@redhat.com>
Tested-by: Toke Høiland-Jørgensen <toke@redhat.com>
Reviewed-by: Victor Nogueira <victor@mojatatu.com>
Tested-by: Victor Nogueira <victor@mojatatu.com>
Acked-by: Jamal Hadi Salim <jhs@mojatatu.com>
Signed-off-by: Rajat Gupta <rajat.gupta@oss.qualcomm.com>
Link: https://patch.msgid.link/20260531123221.48732-1-jhs@mojatatu.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
[Dominique: plenty of context conflict but the code itself could still
mostly be used]
Signed-off-by: Dominique Martinet <dominique.martinet@atmark-techno.com>
---
CVE-2026-46331 got attention and I've had to backport this to 5.10, so
here we are, this should fix it.
Unfortunately there are more conflicts with 5.15 so I didn't do this
one (but if anyone cares older 5.4/4.19 cherry-picks cleanly from this,
they're no longer stable kernels so I won't bother sending)
I've tested in egress with a trivial rule redirecting port inspired from
the man page (and checking on target with tcpdump because return path is
broken), so I hopefully didn't botch this too badly:
tc qdisc replace dev eth0 root handle 1: htb
tc filter add dev eth0 parent 1: u32 match ip dport 23 0xffff action pedit pedit munge ip dport set 22
---
include/net/tc_act/tc_pedit.h | 1 -
net/sched/act_pedit.c | 84 ++++++++++++++++++++++---------------------
2 files changed, 43 insertions(+), 42 deletions(-)
diff --git a/include/net/tc_act/tc_pedit.h b/include/net/tc_act/tc_pedit.h
index 3e02709a1df656931942be4851a115dd6bef8b4c..748cf87a4d7ea5c92b4fd48dd3302b8ad64944fe 100644
--- a/include/net/tc_act/tc_pedit.h
+++ b/include/net/tc_act/tc_pedit.h
@@ -14,7 +14,6 @@ struct tcf_pedit {
struct tc_action common;
unsigned char tcfp_nkeys;
unsigned char tcfp_flags;
- u32 tcfp_off_max_hint;
struct tc_pedit_key *tcfp_keys;
struct tcf_pedit_key_ex *tcfp_keys_ex;
};
diff --git a/net/sched/act_pedit.c b/net/sched/act_pedit.c
index a44101b2f441919b7d4b0be177aa735a3c547350..cebeed6214558b1e1dd717b411c9ffa108a2293d 100644
--- a/net/sched/act_pedit.c
+++ b/net/sched/act_pedit.c
@@ -14,6 +14,8 @@
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
+#include <linux/overflow.h>
+#include <asm/unaligned.h>
#include <net/netlink.h>
#include <net/pkt_sched.h>
#include <linux/tc_act/tc_pedit.h>
@@ -229,21 +231,11 @@ static int tcf_pedit_init(struct net *net, struct nlattr *nla,
p->tcfp_nkeys = parm->nkeys;
}
memcpy(p->tcfp_keys, parm->keys, ksize);
- p->tcfp_off_max_hint = 0;
for (i = 0; i < p->tcfp_nkeys; ++i) {
- u32 cur = p->tcfp_keys[i].off;
-
/* sanitize the shift value for any later use */
p->tcfp_keys[i].shift = min_t(size_t, BITS_PER_TYPE(int) - 1,
p->tcfp_keys[i].shift);
- /* The AT option can read a single byte, we can bound the actual
- * value with uchar max.
- */
- cur += (0xff & p->tcfp_keys[i].offmask) >> p->tcfp_keys[i].shift;
-
- /* Each key touches 4 bytes starting from the computed offset */
- p->tcfp_off_max_hint = max(p->tcfp_off_max_hint, cur + 4);
}
p->tcfp_flags = parm->flags;
@@ -277,15 +269,12 @@ static void tcf_pedit_cleanup(struct tc_action *a)
kfree(p->tcfp_keys_ex);
}
-static bool offset_valid(struct sk_buff *skb, int offset)
+static bool offset_valid(struct sk_buff *skb, int offset, int len)
{
- if (offset > 0 && offset > skb->len)
+ if (offset < -(int)skb_headroom(skb))
return false;
- if (offset < 0 && -offset > skb_headroom(skb))
- return false;
-
- return true;
+ return offset <= (int)skb->len - len;
}
static int pedit_skb_hdr_offset(struct sk_buff *skb,
@@ -325,18 +314,10 @@ static int tcf_pedit_act(struct sk_buff *skb, const struct tc_action *a,
struct tcf_result *res)
{
struct tcf_pedit *p = to_pedit(a);
- u32 max_offset;
int i;
spin_lock(&p->tcf_lock);
- max_offset = (skb_transport_header_was_set(skb) ?
- skb_transport_offset(skb) :
- skb_network_offset(skb)) +
- p->tcfp_off_max_hint;
- if (skb_ensure_writable(skb, min(skb->len, max_offset)))
- goto unlock;
-
tcf_lastuse_update(&p->tcf_tm);
if (p->tcfp_nkeys > 0) {
@@ -347,10 +328,11 @@ static int tcf_pedit_act(struct sk_buff *skb, const struct tc_action *a,
enum pedit_cmd cmd = TCA_PEDIT_KEY_EX_CMD_SET;
for (i = p->tcfp_nkeys; i > 0; i--, tkey++) {
- u32 *ptr, hdata;
+ int write_offset, write_len;
int offset = tkey->off;
int hoffset;
- u32 val;
+ u32 cur_val, val;
+ u32 *ptr;
int rc;
if (tkey_ex) {
@@ -369,13 +351,15 @@ static int tcf_pedit_act(struct sk_buff *skb, const struct tc_action *a,
if (tkey->offmask) {
u8 *d, _d;
+ int at_offset;
- if (!offset_valid(skb, hoffset + tkey->at)) {
- pr_info("tc action pedit 'at' offset %d out of bounds\n",
- hoffset + tkey->at);
+ if (check_add_overflow(hoffset, (int)tkey->at, &at_offset) ||
+ !offset_valid(skb, at_offset, sizeof(_d))) {
+ pr_info_ratelimited("tc action pedit 'at' offset %d out of bounds\n",
+ hoffset + tkey->at);
goto bad;
}
- d = skb_header_pointer(skb, hoffset + tkey->at,
+ d = skb_header_pointer(skb, at_offset,
sizeof(_d), &_d);
if (!d)
goto bad;
@@ -387,23 +371,44 @@ static int tcf_pedit_act(struct sk_buff *skb, const struct tc_action *a,
goto bad;
}
- if (!offset_valid(skb, hoffset + offset)) {
- pr_info("tc action pedit offset %d out of bounds\n",
- hoffset + offset);
+ if (check_add_overflow(hoffset, offset, &write_offset)) {
+ pr_info_ratelimited("tc action pedit offset overflow\n");
goto bad;
}
- ptr = skb_header_pointer(skb, hoffset + offset,
- sizeof(hdata), &hdata);
- if (!ptr)
+ if (!offset_valid(skb, write_offset, sizeof(*ptr))) {
+ pr_info_ratelimited("tc action pedit offset %d out of bounds\n",
+ write_offset);
goto bad;
+ }
+
+ if (write_offset < 0) {
+ if (skb_cow(skb, -write_offset))
+ goto bad;
+ if (write_offset + (int)sizeof(*ptr) > 0) {
+ if (skb_ensure_writable(skb,
+ min_t(int, skb->len,
+ write_offset + (int)sizeof(*ptr))))
+ goto bad;
+ }
+ } else {
+ if (check_add_overflow(write_offset, (int)sizeof(*ptr),
+ &write_len))
+ goto bad;
+ if (skb_ensure_writable(skb, min_t(int, skb->len,
+ write_len)))
+ goto bad;
+ }
+
+ ptr = (u32 *)(skb->data + write_offset);
+ cur_val = get_unaligned(ptr);
/* just do it, baby */
switch (cmd) {
case TCA_PEDIT_KEY_EX_CMD_SET:
val = tkey->val;
break;
case TCA_PEDIT_KEY_EX_CMD_ADD:
- val = (*ptr + tkey->val) & ~tkey->mask;
+ val = (cur_val + tkey->val) & ~tkey->mask;
break;
default:
pr_info("tc action pedit bad command (%d)\n",
@@ -411,9 +416,7 @@ static int tcf_pedit_act(struct sk_buff *skb, const struct tc_action *a,
goto bad;
}
- *ptr = ((*ptr & tkey->mask) ^ val);
- if (ptr == &hdata)
- skb_store_bits(skb, hoffset + offset, ptr, 4);
+ put_unaligned((cur_val & tkey->mask) ^ val, ptr);
}
goto done;
@@ -425,7 +428,6 @@ static int tcf_pedit_act(struct sk_buff *skb, const struct tc_action *a,
p->tcf_qstats.overlimits++;
done:
bstats_update(&p->tcf_bstats, skb);
-unlock:
spin_unlock(&p->tcf_lock);
return p->tcf_action;
}
---
base-commit: d9666dca97c01de1c7395ac53041634eb75120e2
change-id: 20260630-cve-2026-46331-4f851ccf8244
Best regards,
--
Dominique Martinet <dominique.martinet@atmark-techno.com>
^ permalink raw reply related [flat|nested] 4+ messages in thread* [PATCH 5.10.y] net/sched: fix pedit partial COW leading to page cache corruption 2026-06-30 8:05 [PATCH 5.10.y] net/sched: fix pedit partial COW leading to page cache corruption Dominique Martinet @ 2026-06-30 8:27 ` Dominique Martinet 2026-07-02 0:38 ` Sasha Levin 0 siblings, 1 reply; 4+ messages in thread From: Dominique Martinet @ 2026-06-30 8:27 UTC (permalink / raw) To: dominique.martinet, stable Cc: Jamal Hadi Salim, Cong Wang, Jiri Pirko, David S. Miller, Jakub Kicinski, Mat Martineau, Paolo Abeni, netdev, linux-kernel, Rajat Gupta, Yiming Qian, Keenan Dong, Han Guidong, Zhang Cen, Davide Caratti, Toke Høiland-Jørgensen, Victor Nogueira From: Rajat Gupta <rajat.gupta@oss.qualcomm.com> [ Upstream commit 899ee91156e57784090c5565e4f31bd7dbffbc5a ] tcf_pedit_act() computes the COW range for skb_ensure_writable() once before the key loop using tcfp_off_max_hint, but the hint does not account for the runtime header offset added by typed keys. This can leave part of the write region un-COW'd. Fix by moving skb_ensure_writable() inside the per-key loop where the actual write offset is known, and add overflow checking on the offset arithmetic. For negative offsets (e.g. Ethernet header edits at ingress), use skb_cow() to COW the headroom instead. Guard offset_valid() against INT_MIN, where negation is undefined. Fixes: 8b796475fd78 ("net/sched: act_pedit: really ensure the skb is writable") Reported-by: Yiming Qian <yimingqian591@gmail.com> Reported-by: Keenan Dong <keenanat2000@gmail.com> Reported-by: Han Guidong <2045gemini@gmail.com> Reported-by: Zhang Cen <rollkingzzc@gmail.com> Reviewed-by: Han Guidong <2045gemini@gmail.com> Tested-by: Han Guidong <2045gemini@gmail.com> Reviewed-by: Davide Caratti <dcaratti@redhat.com> Tested-by: Davide Caratti <dcaratti@redhat.com> Reviewed-by: Toke Høiland-Jørgensen <toke@redhat.com> Tested-by: Toke Høiland-Jørgensen <toke@redhat.com> Reviewed-by: Victor Nogueira <victor@mojatatu.com> Tested-by: Victor Nogueira <victor@mojatatu.com> Acked-by: Jamal Hadi Salim <jhs@mojatatu.com> Signed-off-by: Rajat Gupta <rajat.gupta@oss.qualcomm.com> Link: https://patch.msgid.link/20260531123221.48732-1-jhs@mojatatu.com Signed-off-by: Jakub Kicinski <kuba@kernel.org> [Dominique: plenty of context conflict but the code itself could still mostly be used] Signed-off-by: Dominique Martinet <dominique.martinet@atmark-techno.com> --- (Sorry for double-send to stable@vger, I hadn't intended to send the Cc-free version...) CVE-2026-46331 got attention and I've had to backport this to 5.10, so here we are, this should fix it. Unfortunately there are more conflicts with 5.15 so I didn't do this one (but if anyone cares older 5.4/4.19 cherry-picks cleanly from this, they're no longer stable kernels so I won't bother sending) I've tested in egress with a trivial rule redirecting port inspired from the man page (and checking on target with tcpdump because return path is broken), so I hopefully didn't botch this too badly: tc qdisc replace dev eth0 root handle 1: htb tc filter add dev eth0 parent 1: u32 match ip dport 23 0xffff action pedit pedit munge ip dport set 22 --- include/net/tc_act/tc_pedit.h | 1 - net/sched/act_pedit.c | 84 ++++++++++++++++++++++--------------------- 2 files changed, 43 insertions(+), 42 deletions(-) diff --git a/include/net/tc_act/tc_pedit.h b/include/net/tc_act/tc_pedit.h index 3e02709a1df656931942be4851a115dd6bef8b4c..748cf87a4d7ea5c92b4fd48dd3302b8ad64944fe 100644 --- a/include/net/tc_act/tc_pedit.h +++ b/include/net/tc_act/tc_pedit.h @@ -14,7 +14,6 @@ struct tcf_pedit { struct tc_action common; unsigned char tcfp_nkeys; unsigned char tcfp_flags; - u32 tcfp_off_max_hint; struct tc_pedit_key *tcfp_keys; struct tcf_pedit_key_ex *tcfp_keys_ex; }; diff --git a/net/sched/act_pedit.c b/net/sched/act_pedit.c index a44101b2f441919b7d4b0be177aa735a3c547350..cebeed6214558b1e1dd717b411c9ffa108a2293d 100644 --- a/net/sched/act_pedit.c +++ b/net/sched/act_pedit.c @@ -14,6 +14,8 @@ #include <linux/module.h> #include <linux/init.h> #include <linux/slab.h> +#include <linux/overflow.h> +#include <asm/unaligned.h> #include <net/netlink.h> #include <net/pkt_sched.h> #include <linux/tc_act/tc_pedit.h> @@ -229,21 +231,11 @@ static int tcf_pedit_init(struct net *net, struct nlattr *nla, p->tcfp_nkeys = parm->nkeys; } memcpy(p->tcfp_keys, parm->keys, ksize); - p->tcfp_off_max_hint = 0; for (i = 0; i < p->tcfp_nkeys; ++i) { - u32 cur = p->tcfp_keys[i].off; - /* sanitize the shift value for any later use */ p->tcfp_keys[i].shift = min_t(size_t, BITS_PER_TYPE(int) - 1, p->tcfp_keys[i].shift); - /* The AT option can read a single byte, we can bound the actual - * value with uchar max. - */ - cur += (0xff & p->tcfp_keys[i].offmask) >> p->tcfp_keys[i].shift; - - /* Each key touches 4 bytes starting from the computed offset */ - p->tcfp_off_max_hint = max(p->tcfp_off_max_hint, cur + 4); } p->tcfp_flags = parm->flags; @@ -277,15 +269,12 @@ static void tcf_pedit_cleanup(struct tc_action *a) kfree(p->tcfp_keys_ex); } -static bool offset_valid(struct sk_buff *skb, int offset) +static bool offset_valid(struct sk_buff *skb, int offset, int len) { - if (offset > 0 && offset > skb->len) + if (offset < -(int)skb_headroom(skb)) return false; - if (offset < 0 && -offset > skb_headroom(skb)) - return false; - - return true; + return offset <= (int)skb->len - len; } static int pedit_skb_hdr_offset(struct sk_buff *skb, @@ -325,18 +314,10 @@ static int tcf_pedit_act(struct sk_buff *skb, const struct tc_action *a, struct tcf_result *res) { struct tcf_pedit *p = to_pedit(a); - u32 max_offset; int i; spin_lock(&p->tcf_lock); - max_offset = (skb_transport_header_was_set(skb) ? - skb_transport_offset(skb) : - skb_network_offset(skb)) + - p->tcfp_off_max_hint; - if (skb_ensure_writable(skb, min(skb->len, max_offset))) - goto unlock; - tcf_lastuse_update(&p->tcf_tm); if (p->tcfp_nkeys > 0) { @@ -347,10 +328,11 @@ static int tcf_pedit_act(struct sk_buff *skb, const struct tc_action *a, enum pedit_cmd cmd = TCA_PEDIT_KEY_EX_CMD_SET; for (i = p->tcfp_nkeys; i > 0; i--, tkey++) { - u32 *ptr, hdata; + int write_offset, write_len; int offset = tkey->off; int hoffset; - u32 val; + u32 cur_val, val; + u32 *ptr; int rc; if (tkey_ex) { @@ -369,13 +351,15 @@ static int tcf_pedit_act(struct sk_buff *skb, const struct tc_action *a, if (tkey->offmask) { u8 *d, _d; + int at_offset; - if (!offset_valid(skb, hoffset + tkey->at)) { - pr_info("tc action pedit 'at' offset %d out of bounds\n", - hoffset + tkey->at); + if (check_add_overflow(hoffset, (int)tkey->at, &at_offset) || + !offset_valid(skb, at_offset, sizeof(_d))) { + pr_info_ratelimited("tc action pedit 'at' offset %d out of bounds\n", + hoffset + tkey->at); goto bad; } - d = skb_header_pointer(skb, hoffset + tkey->at, + d = skb_header_pointer(skb, at_offset, sizeof(_d), &_d); if (!d) goto bad; @@ -387,23 +371,44 @@ static int tcf_pedit_act(struct sk_buff *skb, const struct tc_action *a, goto bad; } - if (!offset_valid(skb, hoffset + offset)) { - pr_info("tc action pedit offset %d out of bounds\n", - hoffset + offset); + if (check_add_overflow(hoffset, offset, &write_offset)) { + pr_info_ratelimited("tc action pedit offset overflow\n"); goto bad; } - ptr = skb_header_pointer(skb, hoffset + offset, - sizeof(hdata), &hdata); - if (!ptr) + if (!offset_valid(skb, write_offset, sizeof(*ptr))) { + pr_info_ratelimited("tc action pedit offset %d out of bounds\n", + write_offset); goto bad; + } + + if (write_offset < 0) { + if (skb_cow(skb, -write_offset)) + goto bad; + if (write_offset + (int)sizeof(*ptr) > 0) { + if (skb_ensure_writable(skb, + min_t(int, skb->len, + write_offset + (int)sizeof(*ptr)))) + goto bad; + } + } else { + if (check_add_overflow(write_offset, (int)sizeof(*ptr), + &write_len)) + goto bad; + if (skb_ensure_writable(skb, min_t(int, skb->len, + write_len))) + goto bad; + } + + ptr = (u32 *)(skb->data + write_offset); + cur_val = get_unaligned(ptr); /* just do it, baby */ switch (cmd) { case TCA_PEDIT_KEY_EX_CMD_SET: val = tkey->val; break; case TCA_PEDIT_KEY_EX_CMD_ADD: - val = (*ptr + tkey->val) & ~tkey->mask; + val = (cur_val + tkey->val) & ~tkey->mask; break; default: pr_info("tc action pedit bad command (%d)\n", @@ -411,9 +416,7 @@ static int tcf_pedit_act(struct sk_buff *skb, const struct tc_action *a, goto bad; } - *ptr = ((*ptr & tkey->mask) ^ val); - if (ptr == &hdata) - skb_store_bits(skb, hoffset + offset, ptr, 4); + put_unaligned((cur_val & tkey->mask) ^ val, ptr); } goto done; @@ -425,7 +428,6 @@ static int tcf_pedit_act(struct sk_buff *skb, const struct tc_action *a, p->tcf_qstats.overlimits++; done: bstats_update(&p->tcf_bstats, skb); -unlock: spin_unlock(&p->tcf_lock); return p->tcf_action; } --- base-commit: d9666dca97c01de1c7395ac53041634eb75120e2 change-id: 20260630-cve-2026-46331-4f851ccf8244 Best regards, -- Dominique Martinet <dominique.martinet@atmark-techno.com> ^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH 5.10.y] net/sched: fix pedit partial COW leading to page cache corruption 2026-06-30 8:27 ` Dominique Martinet @ 2026-07-02 0:38 ` Sasha Levin 2026-07-02 1:00 ` Dominique Martinet 0 siblings, 1 reply; 4+ messages in thread From: Sasha Levin @ 2026-07-02 0:38 UTC (permalink / raw) To: dominique.martinet, stable Cc: Sasha Levin, Jamal Hadi Salim, Cong Wang, Jiri Pirko, David S. Miller, Jakub Kicinski, Mat Martineau, Paolo Abeni, netdev, linux-kernel, Rajat Gupta, Yiming Qian, Keenan Dong, Han Guidong, Zhang Cen, Davide Caratti, Toke Høiland-Jørgensen, Victor Nogueira > [Dominique: plenty of context conflict but the code itself could still > mostly be used] Thanks! However, the same upstream fix (899ee91156e5) is already queued for 5.10.y via Wentao Guan's backport, which brings in the act_pedit RCU/percpu-stats prerequisites first and then applies the fix nearly verbatim. -- Thanks, Sasha ^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH 5.10.y] net/sched: fix pedit partial COW leading to page cache corruption 2026-07-02 0:38 ` Sasha Levin @ 2026-07-02 1:00 ` Dominique Martinet 0 siblings, 0 replies; 4+ messages in thread From: Dominique Martinet @ 2026-07-02 1:00 UTC (permalink / raw) To: Sasha Levin Cc: stable, Jamal Hadi Salim, Cong Wang, Jiri Pirko, David S. Miller, Jakub Kicinski, Mat Martineau, Paolo Abeni, netdev, linux-kernel, Rajat Gupta, Yiming Qian, Keenan Dong, Han Guidong, Zhang Cen, Davide Caratti, Toke Høiland-Jørgensen, Victor Nogueira Sasha Levin wrote on Wed, Jul 01, 2026 at 08:38:34PM -0400: > > [Dominique: plenty of context conflict but the code itself could still > > mostly be used] > > Thanks! However, the same upstream fix (899ee91156e5) is already queued for > 5.10.y via Wentao Guan's backport, which brings in the act_pedit > RCU/percpu-stats prerequisites first and then applies the fix nearly > verbatim. Oh, thanks! Sorry for the noise, I'll remember to check next time. -- Dominique ^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-07-02 1:00 UTC | newest] Thread overview: 4+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-06-30 8:05 [PATCH 5.10.y] net/sched: fix pedit partial COW leading to page cache corruption Dominique Martinet 2026-06-30 8:27 ` Dominique Martinet 2026-07-02 0:38 ` Sasha Levin 2026-07-02 1:00 ` Dominique Martinet
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox