Package: libsocket-msghdr-perl Version: 0.05-2+b4 Severity: normal Tags: patch upstream
Dear Maintainer, recvmsg in Socket::MsgHdr fails to set the `flags' value to what the recvmsg(2) syscall writes to `struct msghdr'.msg_flags. This makes it impossible to detect truncation errors such as MSG_TRUNC and MSG_CTRUNC when the `buflen' and `controllen' fields are too small. Attached is a patch which fixes the issue in upstream. Unfortunately, I cannot contribute directly to upstream due to Terms of Service and JS requirement of Github causing accessibility problems.
>From af9d3624155ab51cd752f27981ef3b7287d24153 Mon Sep 17 00:00:00 2001 From: Eric Wong <[email protected]> Date: Thu, 23 Jul 2026 20:54:15 +0000 Subject: [PATCH] recvmsg: set `flags' field according to .msg_flags The .msg_flags field of `struct msghdr' gets populated by the recvmsg(2) syscall. Most notably, .msg_flags allows the receiver to detect truncation via MSG_TRUNC and MSG_CTRUNC flags when the supplied `buflen' and/or `controllen' fields are too small. Note that MSG_TRUNC set in .msg_flags by recvfrom is different from the `flags' argument. MSG_TRUNC in the `flags' argument changes the return value of recvfrom(2) to the intended length of the data, whereas .msg_flags merely tells the caller the buffer is too small and does not tell the intended length. MSG_TRUNC in the `flags' argument probably makes the most sense with MSG_PEEK, as shown in the below example to dynamically size `buflen' to the necessary value to avoid MSG_TRUNC in .msg_flags in a subsequent recvmsg call: use Socket; use Socket::MsgHdr; my ($rd, $wr); socketpair($rd, $wr, AF_UNIX, SOCK_DGRAM, 0) or die "socketpair: $!"; my $hdr = Socket::MsgHdr->new(buf => 'ab'); sendmsg($wr, $hdr, 0) or die "sendmsg: $!"; my $m = Socket::MsgHdr->new(buflen => 1); my $r = recvmsg($rd, $m, MSG_TRUNC|MSG_PEEK) or die "recvmsg: $!"; print "MSG_TRUNC=", MSG_TRUNC, "\n"; print "before: r=$r flags=", $m->flags, " (expecting (flags & MSG_TRUNC=${\MSG_TRUNC}))\n"; $m = Socket::MsgHdr->new(buflen => $r); recvmsg($rd, $m, 0) or die "recvmsg: $!"; print " after: r=$r flags=", $m->flags, " (expecting flags==0)\n"; --- MsgHdr.xs | 4 ++++ t/30recv.t | 40 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/MsgHdr.xs b/MsgHdr.xs index 0e4b932..9b7123a 100644 --- a/MsgHdr.xs +++ b/MsgHdr.xs @@ -158,6 +158,10 @@ smh_recvmsg(s, msg_hdr, flags = 0) SvCUR_set(*svp, RETVAL); if ((svp = hv_fetch(hsh, "control", 7, FALSE))) SvCUR_set(*svp, mh.m.msg_controllen); + if ((svp = hv_fetch(hsh, "flags", 5, FALSE))) { + SvUPGRADE(*svp, SVt_IV); + SvIV_set(*svp, mh.m.msg_flags); + } } OUTPUT: RETVAL diff --git a/t/30recv.t b/t/30recv.t index 7bb2b8e..c12daa4 100644 --- a/t/30recv.t +++ b/t/30recv.t @@ -6,7 +6,7 @@ # change 'tests => 1' to 'tests => last_test_to_print'; use Test::More; -BEGIN { plan tests => 9 }; +BEGIN { plan tests => 11 }; use bytes; use strict; use Socket; @@ -124,4 +124,42 @@ SKIP: { cmp_ok($dev1, '==', $dev2, "scm_rights fds on same device"); cmp_ok($ino1, '==', $ino2, "scm_rights fds are same inode"); + + eval { &MSG_CTRUNC; }; # autoloaded, may not be defined yet + skip "msg_ctrunc not defined", 1 if $@; + + socketpair(Rd, Wr, AF_UNIX, SOCK_DGRAM, 0) + or die "socketpair: $!\n"; + + $hdr = new Socket::MsgHdr(buf => "hello!"); + $hdr->cmsghdr(SOL_SOCKET, SCM_RIGHTS, pack('i', fileno STDIN)); + sendmsg(\*Wr, $hdr, 0) + or die "sendmsg: $!\n"; + + $m = new Socket::MsgHdr(buflen => 256, controllen => 1); + recvmsg(\*Rd, $m, 0) + or die "recvmsg: $!\n"; + + ok($m->flags & MSG_CTRUNC, 'MSG_CTRUNC set when controllen is too short'); + + close Rd; close Wr; +}; + +# 11 +SKIP: { + local (*Rd, *Wr); + socketpair(Rd, Wr, AF_UNIX, SOCK_DGRAM, 0) + or die "socketpair: $!\n"; + + my $hdr = new Socket::MsgHdr(buf => 'ab'); + sendmsg(\*Wr, $hdr, 0) + or die "sendmsg: $!\n"; + + my $m = new Socket::MsgHdr(buflen => 1); + recvmsg(\*Rd, $m, 0) + or die "recvmsg: $!\n"; + + ok($m->flags & MSG_TRUNC, 'MSG_TRUNC set when buflen is too short'); + + close Rd; close Wr; };

