diff options
author | Rahat Mahmood <rahat@google.com> | 2021-08-05 20:13:54 -0700 |
---|---|---|
committer | gVisor bot <gvisor-bot@google.com> | 2021-08-05 20:16:54 -0700 |
commit | 569f605f438dd10e5ffa1d5eb129ba1d15bbf34c (patch) | |
tree | e1d6fe4b8fca8b51ac96c2c28565d33ced978587 /test | |
parent | 15853bdc88523021f89261860eaebab06bb19dbf (diff) |
Correctly handle interruptions in blocking msgqueue syscalls.
Reported-by: syzbot+63bde04529f701c76168@syzkaller.appspotmail.com
Reported-by: syzbot+69866b9a16ec29993e6a@syzkaller.appspotmail.com
PiperOrigin-RevId: 389084629
Diffstat (limited to 'test')
-rw-r--r-- | test/syscalls/linux/BUILD | 3 | ||||
-rw-r--r-- | test/syscalls/linux/msgqueue.cc | 138 |
2 files changed, 118 insertions, 23 deletions
diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index b7e83b5de..71cc9b51d 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -4173,10 +4173,11 @@ cc_binary( linkstatic = 1, deps = [ "//test/util:capability_util", + "//test/util:signal_util", "//test/util:temp_path", - "//test/util:test_main", "//test/util:test_util", "//test/util:thread_util", + "@com_google_absl//absl/synchronization", "@com_google_absl//absl/time", ], ) diff --git a/test/syscalls/linux/msgqueue.cc b/test/syscalls/linux/msgqueue.cc index cc4022077..8994b739a 100644 --- a/test/syscalls/linux/msgqueue.cc +++ b/test/syscalls/linux/msgqueue.cc @@ -13,12 +13,15 @@ // limitations under the License. #include <errno.h> +#include <signal.h> #include <sys/ipc.h> #include <sys/msg.h> #include <sys/types.h> +#include "absl/synchronization/notification.h" #include "absl/time/clock.h" #include "test/util/capability_util.h" +#include "test/util/signal_util.h" #include "test/util/temp_path.h" #include "test/util/test_util.h" #include "test/util/thread_util.h" @@ -31,6 +34,8 @@ constexpr int msgMax = 8192; // Max size for message in bytes. constexpr int msgMni = 32000; // Max number of identifiers. constexpr int msgMnb = 16384; // Default max size of message queue in bytes. +constexpr int kInterruptSignal = SIGALRM; + // Queue is a RAII class used to automatically clean message queues. class Queue { public: @@ -73,6 +78,12 @@ bool operator==(msgbuf& a, msgbuf& b) { return a.mtype == b.mtype; } +// msgmax represents a buffer for the largest possible single message. +struct msgmax { + int64_t mtype; + char mtext[msgMax]; +}; + // Test simple creation and retrieval for msgget(2). TEST(MsgqueueTest, MsgGet) { const TempPath keyfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); @@ -310,13 +321,6 @@ TEST(MsgqueueTest, MsgOpLimits) { SyscallFailsWithErrno(EINVAL)); // Limit for queue. - // Use a buffer with the maximum mount of bytes that can be transformed to - // make it easier to exhaust the queue limit. - struct msgmax { - int64_t mtype; - char mtext[msgMax]; - }; - msgmax limit{1, ""}; for (size_t i = 0, msgCount = msgMnb / msgMax; i < msgCount; i++) { EXPECT_THAT(msgsnd(queue.get(), &limit, sizeof(limit.mtext), 0), @@ -470,13 +474,6 @@ TEST(MsgqueueTest, MsgSndBlocking) { Queue queue(msgget(IPC_PRIVATE, 0600)); ASSERT_THAT(queue.get(), SyscallSucceeds()); - // Use a buffer with the maximum mount of bytes that can be transformed to - // make it easier to exhaust the queue limit. - struct msgmax { - int64_t mtype; - char mtext[msgMax]; - }; - msgmax buf{1, ""}; // Has max amount of bytes. const size_t msgCount = msgMnb / msgMax; // Number of messages that can be @@ -494,6 +491,8 @@ TEST(MsgqueueTest, MsgSndBlocking) { SyscallSucceeds()); }); + const DisableSave ds; // Too many syscalls. + // To increase the chance of the last msgsnd blocking before doing a msgrcv, // we use MSG_COPY option to copy the last index in the queue. As long as // MSG_COPY fails, the queue hasn't yet been filled. When MSG_COPY succeeds, @@ -516,15 +515,9 @@ TEST(MsgqueueTest, MsgSndRmWhileBlocking) { Queue queue(msgget(IPC_PRIVATE, 0600)); ASSERT_THAT(queue.get(), SyscallSucceeds()); - // Use a buffer with the maximum mount of bytes that can be transformed to - // make it easier to exhaust the queue limit. - struct msgmax { - int64_t mtype; - char mtext[msgMax]; - }; + // Number of messages that can be sent without blocking. + const size_t msgCount = msgMnb / msgMax; - const size_t msgCount = msgMnb / msgMax; // Number of messages that can be - // sent without blocking. ScopedThread t([&] { // Fill the queue. msgmax buf{1, ""}; @@ -540,6 +533,8 @@ TEST(MsgqueueTest, MsgSndRmWhileBlocking) { EXPECT_TRUE((errno == EIDRM || errno == EINVAL)); }); + const DisableSave ds; // Too many syscalls. + // Similar to MsgSndBlocking, we do this to increase the chance of msgsnd // blocking before removing the queue. msgmax rcv; @@ -627,6 +622,105 @@ TEST(MsgqueueTest, MsgOpGeneral) { ScopedThread s10(sender(4)); } +void empty_sighandler(int sig, siginfo_t* info, void* context) {} + +TEST(MsgqueueTest, InterruptRecv) { + Queue queue(msgget(IPC_PRIVATE, 0600)); + char buf[64]; + + absl::Notification done, exit; + + // Thread calling msgrcv with no corresponding send. It would block forever, + // but we'll interrupt with a signal below. + ScopedThread t([&] { + struct sigaction sa = {}; + sa.sa_sigaction = empty_sighandler; + sigfillset(&sa.sa_mask); + sa.sa_flags = SA_SIGINFO; + auto cleanup_sigaction = + ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(kInterruptSignal, sa)); + auto sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE( + ScopedSignalMask(SIG_UNBLOCK, kInterruptSignal)); + + EXPECT_THAT(msgrcv(queue.get(), &buf, sizeof(buf), 0, 0), + SyscallFailsWithErrno(EINTR)); + + done.Notify(); + exit.WaitForNotification(); + }); + + const DisableSave ds; // Too many syscalls. + + // We want the signal to arrive while msgrcv is blocking, but not after the + // thread has exited. Signals that arrive before msgrcv are no-ops. + do { + EXPECT_THAT(kill(getpid(), kInterruptSignal), SyscallSucceeds()); + absl::SleepFor(absl::Milliseconds(100)); // Rate limit. + } while (!done.HasBeenNotified()); + + exit.Notify(); + t.Join(); +} + +TEST(MsgqueueTest, InterruptSend) { + Queue queue(msgget(IPC_PRIVATE, 0600)); + msgmax buf{1, ""}; + // Number of messages that can be sent without blocking. + const size_t msgCount = msgMnb / msgMax; + + // Fill the queue. + for (size_t i = 0; i < msgCount; i++) { + ASSERT_THAT(msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0), + SyscallSucceeds()); + } + + absl::Notification done, exit; + + // Thread calling msgsnd on a full queue. It would block forever, but we'll + // interrupt with a signal below. + ScopedThread t([&] { + struct sigaction sa = {}; + sa.sa_sigaction = empty_sighandler; + sigfillset(&sa.sa_mask); + sa.sa_flags = SA_SIGINFO; + auto cleanup_sigaction = + ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(kInterruptSignal, sa)); + auto sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE( + ScopedSignalMask(SIG_UNBLOCK, kInterruptSignal)); + + EXPECT_THAT(msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0), + SyscallFailsWithErrno(EINTR)); + + done.Notify(); + exit.WaitForNotification(); + }); + + const DisableSave ds; // Too many syscalls. + + // We want the signal to arrive while msgsnd is blocking, but not after the + // thread has exited. Signals that arrive before msgsnd are no-ops. + do { + EXPECT_THAT(kill(getpid(), kInterruptSignal), SyscallSucceeds()); + absl::SleepFor(absl::Milliseconds(100)); // Rate limit. + } while (!done.HasBeenNotified()); + + exit.Notify(); + t.Join(); +} + } // namespace } // namespace testing } // namespace gvisor + +int main(int argc, char** argv) { + // Some tests depend on delivering a signal to the main thread. Block the + // target signal so that any other threads created by TestInit will also have + // the signal blocked. + sigset_t set; + sigemptyset(&set); + sigaddset(&set, gvisor::testing::kInterruptSignal); + TEST_PCHECK(sigprocmask(SIG_BLOCK, &set, nullptr) == 0); + + gvisor::testing::TestInit(&argc, &argv); + return gvisor::testing::RunAllTests(); +} |