diff mbox series

[net] tcp: devmem: properly export MSG_CTRUNC to userspace

Message ID 20250218194056.380647-1-sdf@fomichev.me (mailing list archive)
State Changes Requested
Delegated to: Netdev Maintainers
Headers show
Series [net] tcp: devmem: properly export MSG_CTRUNC to userspace | expand

Checks

Context Check Description
netdev/series_format success Single patches do not need cover letters
netdev/tree_selection success Clearly marked for net
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag present in non-next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 0 this patch: 0
netdev/build_tools success No tools touched, skip
netdev/cc_maintainers fail 1 blamed authors not CCed: asml.silence@gmail.com; 1 maintainers not CCed: asml.silence@gmail.com
netdev/build_clang success Errors and warnings before: 1 this patch: 1
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success Fixes tag looks correct
netdev/build_allmodconfig_warn success Errors and warnings before: 0 this patch: 0
netdev/checkpatch success total: 0 errors, 0 warnings, 0 checks, 14 lines checked
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 1 this patch: 1
netdev/source_inline success Was 0 now: 0
netdev/contest success net-next-2025-02-18--21-00 (tests: 891)

Commit Message

Stanislav Fomichev Feb. 18, 2025, 7:40 p.m. UTC
Currently, we report -ETOOSMALL (err) only on the first iteration
(!sent). When we get put_cmsg error after a bunch of successful
put_cmsg calls, we don't signal the error at all. This might be
confusing on the userspace side which will see truncated CMSGs
but no MSG_CTRUNC signal.

Consider the following case:
- sizeof(struct cmsghdr) = 16
- sizeof(struct dmabuf_cmsg) = 24
- total cmsg size (CMSG_LEN) = 40 (16+24)

When calling recvmsg with msg_controllen=60, the userspace
will receive two(!) dmabuf_cmsg(s), the first one will
be a valid one and the second one will be silently truncated. There is no
easy way to discover the truncation besides doing something like
"cm->cmsg_len != CMSG_LEN(sizeof(dmabuf_cmsg))". Do not mask MSG_CTRUNC
to keep conventional cmsg-truncated signals in place.

If there is a concern with breaking UAPI, I can document this case in
devmem.rst instead.

Fixes: 8f0b3cc9a4c1 ("tcp: RX path for devmem TCP")
Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
---
 net/ipv4/tcp.c | 2 --
 1 file changed, 2 deletions(-)

Comments

Mina Almasry Feb. 18, 2025, 8:10 p.m. UTC | #1
On Tue, Feb 18, 2025 at 11:40 AM Stanislav Fomichev <sdf@fomichev.me> wrote:
>
> Currently, we report -ETOOSMALL (err) only on the first iteration
> (!sent). When we get put_cmsg error after a bunch of successful
> put_cmsg calls, we don't signal the error at all. This might be
> confusing on the userspace side which will see truncated CMSGs
> but no MSG_CTRUNC signal.
>
> Consider the following case:
> - sizeof(struct cmsghdr) = 16
> - sizeof(struct dmabuf_cmsg) = 24
> - total cmsg size (CMSG_LEN) = 40 (16+24)
>
> When calling recvmsg with msg_controllen=60, the userspace
> will receive two(!) dmabuf_cmsg(s), the first one will

The intended API in this scenario is that the user will receive *one*
dmabuf_cmgs. The kernel will consider that data in that frag to be
delivered to userspace, and subsequent recvmsg() calls will not
re-deliver that data. The next recvmsg() call will deliver the data
that we failed to put_cmsg() in the current call.

If you receive two dmabuf_cmsgs in this scenario, that is indeed a
bug. Exposing CMSG_CTRUNC could be a good fix. It may indicate to the
user "ignore the last cmsg we put, because it got truncated, and
you'll receive the full cmsg on the next recvmsg call". We do need to
update the docs for this I think.

However, I think a much much better fix is to modify put_cmsg() so
that we only get one dmabuf_cmsgs in this scenario, if possible. We
could add a strict flag to put_cmsg(). If (strict == true &&
msg->controlllen < cmlen), we return an error instead of putting a
truncated cmsg, so that the user only sees one dmabuf_cmsg in this
scenario.

Is this doable?

--
Thanks,
Mina
Stanislav Fomichev Feb. 18, 2025, 9:17 p.m. UTC | #2
On 02/18, Mina Almasry wrote:
> On Tue, Feb 18, 2025 at 11:40 AM Stanislav Fomichev <sdf@fomichev.me> wrote:
> >
> > Currently, we report -ETOOSMALL (err) only on the first iteration
> > (!sent). When we get put_cmsg error after a bunch of successful
> > put_cmsg calls, we don't signal the error at all. This might be
> > confusing on the userspace side which will see truncated CMSGs
> > but no MSG_CTRUNC signal.
> >
> > Consider the following case:
> > - sizeof(struct cmsghdr) = 16
> > - sizeof(struct dmabuf_cmsg) = 24
> > - total cmsg size (CMSG_LEN) = 40 (16+24)
> >
> > When calling recvmsg with msg_controllen=60, the userspace
> > will receive two(!) dmabuf_cmsg(s), the first one will
> 
> The intended API in this scenario is that the user will receive *one*
> dmabuf_cmgs. The kernel will consider that data in that frag to be
> delivered to userspace, and subsequent recvmsg() calls will not
> re-deliver that data. The next recvmsg() call will deliver the data
> that we failed to put_cmsg() in the current call.
> 
> If you receive two dmabuf_cmsgs in this scenario, that is indeed a
> bug. Exposing CMSG_CTRUNC could be a good fix. It may indicate to the
> user "ignore the last cmsg we put, because it got truncated, and
> you'll receive the full cmsg on the next recvmsg call". We do need to
> update the docs for this I think.
> 
> However, I think a much much better fix is to modify put_cmsg() so
> that we only get one dmabuf_cmsgs in this scenario, if possible. We
> could add a strict flag to put_cmsg(). If (strict == true &&
> msg->controlllen < cmlen), we return an error instead of putting a
> truncated cmsg, so that the user only sees one dmabuf_cmsg in this
> scenario.
> 
> Is this doable?

Instead of modifying put_cmsg(), I can have an extra check before
calling it to make sure the full entry fits. Something like:

--- a/net/ipv4/tcp.c
+++ b/net/ipv4/tcp.c
@@ -2498,6 +2498,11 @@ static int tcp_recvmsg_dmabuf(struct sock *sk, const struct sk_buff *skb,
 				offset += copy;
 				remaining_len -= copy;
 
+				if (msg.msg_controllen < CMSG_LEN(sizeof(dmabuf_cmsg))) {
+					err = -ETOOSMALL;
+					goto out;
+				}
+
 				err = put_cmsg(msg, SOL_SOCKET,
 					       SO_DEVMEM_DMABUF,
 					       sizeof(dmabuf_cmsg),

WDYT? I'll still probably remove '~MSG_CTRUNC' parts as well to avoid
confusion.
Mina Almasry Feb. 18, 2025, 9:51 p.m. UTC | #3
On Tue, Feb 18, 2025 at 1:17 PM Stanislav Fomichev <stfomichev@gmail.com> wrote:
>
> On 02/18, Mina Almasry wrote:
> > On Tue, Feb 18, 2025 at 11:40 AM Stanislav Fomichev <sdf@fomichev.me> wrote:
> > >
> > > Currently, we report -ETOOSMALL (err) only on the first iteration
> > > (!sent). When we get put_cmsg error after a bunch of successful
> > > put_cmsg calls, we don't signal the error at all. This might be
> > > confusing on the userspace side which will see truncated CMSGs
> > > but no MSG_CTRUNC signal.
> > >
> > > Consider the following case:
> > > - sizeof(struct cmsghdr) = 16
> > > - sizeof(struct dmabuf_cmsg) = 24
> > > - total cmsg size (CMSG_LEN) = 40 (16+24)
> > >
> > > When calling recvmsg with msg_controllen=60, the userspace
> > > will receive two(!) dmabuf_cmsg(s), the first one will
> >
> > The intended API in this scenario is that the user will receive *one*
> > dmabuf_cmgs. The kernel will consider that data in that frag to be
> > delivered to userspace, and subsequent recvmsg() calls will not
> > re-deliver that data. The next recvmsg() call will deliver the data
> > that we failed to put_cmsg() in the current call.
> >
> > If you receive two dmabuf_cmsgs in this scenario, that is indeed a
> > bug. Exposing CMSG_CTRUNC could be a good fix. It may indicate to the
> > user "ignore the last cmsg we put, because it got truncated, and
> > you'll receive the full cmsg on the next recvmsg call". We do need to
> > update the docs for this I think.
> >
> > However, I think a much much better fix is to modify put_cmsg() so
> > that we only get one dmabuf_cmsgs in this scenario, if possible. We
> > could add a strict flag to put_cmsg(). If (strict == true &&
> > msg->controlllen < cmlen), we return an error instead of putting a
> > truncated cmsg, so that the user only sees one dmabuf_cmsg in this
> > scenario.
> >
> > Is this doable?
>
> Instead of modifying put_cmsg(), I can have an extra check before
> calling it to make sure the full entry fits. Something like:
>

Yes, that sounds perfect. I would add a new helper, maybe
put_dmabuf_cmsg, that checks that we have enough space before calling
the generic put_cmsg().

> --- a/net/ipv4/tcp.c
> +++ b/net/ipv4/tcp.c
> @@ -2498,6 +2498,11 @@ static int tcp_recvmsg_dmabuf(struct sock *sk, const struct sk_buff *skb,
>                                 offset += copy;
>                                 remaining_len -= copy;
>
> +                               if (msg.msg_controllen < CMSG_LEN(sizeof(dmabuf_cmsg))) {
> +                                       err = -ETOOSMALL;
> +                                       goto out;
> +                               }
> +
>                                 err = put_cmsg(msg, SOL_SOCKET,
>                                                SO_DEVMEM_DMABUF,
>                                                sizeof(dmabuf_cmsg),
>
> WDYT? I'll still probably remove '~MSG_CTRUNC' parts as well to avoid
> confusion.

Yes, since we check there is enough space before calling put_cmsg(),
it should now become impossible for put_cmsg() to set MSG_CTRUNC
anyway, so the check in tcp_recvmsg_dmabuf() becomes an unnecessary
defensive check that should be removed.

Thanks for catching this!
Stanislav Fomichev Feb. 18, 2025, 10:14 p.m. UTC | #4
On 02/18, Mina Almasry wrote:
> On Tue, Feb 18, 2025 at 1:17 PM Stanislav Fomichev <stfomichev@gmail.com> wrote:
> >
> > On 02/18, Mina Almasry wrote:
> > > On Tue, Feb 18, 2025 at 11:40 AM Stanislav Fomichev <sdf@fomichev.me> wrote:
> > > >
> > > > Currently, we report -ETOOSMALL (err) only on the first iteration
> > > > (!sent). When we get put_cmsg error after a bunch of successful
> > > > put_cmsg calls, we don't signal the error at all. This might be
> > > > confusing on the userspace side which will see truncated CMSGs
> > > > but no MSG_CTRUNC signal.
> > > >
> > > > Consider the following case:
> > > > - sizeof(struct cmsghdr) = 16
> > > > - sizeof(struct dmabuf_cmsg) = 24
> > > > - total cmsg size (CMSG_LEN) = 40 (16+24)
> > > >
> > > > When calling recvmsg with msg_controllen=60, the userspace
> > > > will receive two(!) dmabuf_cmsg(s), the first one will
> > >
> > > The intended API in this scenario is that the user will receive *one*
> > > dmabuf_cmgs. The kernel will consider that data in that frag to be
> > > delivered to userspace, and subsequent recvmsg() calls will not
> > > re-deliver that data. The next recvmsg() call will deliver the data
> > > that we failed to put_cmsg() in the current call.
> > >
> > > If you receive two dmabuf_cmsgs in this scenario, that is indeed a
> > > bug. Exposing CMSG_CTRUNC could be a good fix. It may indicate to the
> > > user "ignore the last cmsg we put, because it got truncated, and
> > > you'll receive the full cmsg on the next recvmsg call". We do need to
> > > update the docs for this I think.
> > >
> > > However, I think a much much better fix is to modify put_cmsg() so
> > > that we only get one dmabuf_cmsgs in this scenario, if possible. We
> > > could add a strict flag to put_cmsg(). If (strict == true &&
> > > msg->controlllen < cmlen), we return an error instead of putting a
> > > truncated cmsg, so that the user only sees one dmabuf_cmsg in this
> > > scenario.
> > >
> > > Is this doable?
> >
> > Instead of modifying put_cmsg(), I can have an extra check before
> > calling it to make sure the full entry fits. Something like:
> >
> 
> Yes, that sounds perfect. I would add a new helper, maybe
> put_dmabuf_cmsg, that checks that we have enough space before calling
> the generic put_cmsg().
> 
> > --- a/net/ipv4/tcp.c
> > +++ b/net/ipv4/tcp.c
> > @@ -2498,6 +2498,11 @@ static int tcp_recvmsg_dmabuf(struct sock *sk, const struct sk_buff *skb,
> >                                 offset += copy;
> >                                 remaining_len -= copy;
> >
> > +                               if (msg.msg_controllen < CMSG_LEN(sizeof(dmabuf_cmsg))) {
> > +                                       err = -ETOOSMALL;
> > +                                       goto out;
> > +                               }
> > +
> >                                 err = put_cmsg(msg, SOL_SOCKET,
> >                                                SO_DEVMEM_DMABUF,
> >                                                sizeof(dmabuf_cmsg),
> >
> > WDYT? I'll still probably remove '~MSG_CTRUNC' parts as well to avoid
> > confusion.
> 
> Yes, since we check there is enough space before calling put_cmsg(),
> it should now become impossible for put_cmsg() to set MSG_CTRUNC
> anyway, so the check in tcp_recvmsg_dmabuf() becomes an unnecessary
> defensive check that should be removed.
> 
> Thanks for catching this!

Perfect, thanks for a quick review!

---
pw-bot: cr
diff mbox series

Patch

diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c
index 0d704bda6c41..bdc9ac648d83 100644
--- a/net/ipv4/tcp.c
+++ b/net/ipv4/tcp.c
@@ -2441,7 +2441,6 @@  static int tcp_recvmsg_dmabuf(struct sock *sk, const struct sk_buff *skb,
 			err = put_cmsg(msg, SOL_SOCKET, SO_DEVMEM_LINEAR,
 				       sizeof(dmabuf_cmsg), &dmabuf_cmsg);
 			if (err || msg->msg_flags & MSG_CTRUNC) {
-				msg->msg_flags &= ~MSG_CTRUNC;
 				if (!err)
 					err = -ETOOSMALL;
 				goto out;
@@ -2504,7 +2503,6 @@  static int tcp_recvmsg_dmabuf(struct sock *sk, const struct sk_buff *skb,
 					       sizeof(dmabuf_cmsg),
 					       &dmabuf_cmsg);
 				if (err || msg->msg_flags & MSG_CTRUNC) {
-					msg->msg_flags &= ~MSG_CTRUNC;
 					if (!err)
 						err = -ETOOSMALL;
 					goto out;