Re: [PATCH v8 3/3] test: Add test for pidfd getfd

From: Christian Brauner
Date: Tue Jan 07 2020 - 03:55:52 EST


On Mon, Jan 06, 2020 at 09:06:47PM +0000, Sargun Dhillon wrote:
> On Mon, Jan 06, 2020 at 06:19:41PM +0100, Christian Brauner wrote:
> > On Sun, Jan 05, 2020 at 07:08:13PM +0000, Sargun Dhillon wrote:
> > > On Sun, Jan 05, 2020 at 03:20:23PM +0100, Christian Brauner wrote:
> > > > On Fri, Jan 03, 2020 at 08:29:28AM -0800, Sargun Dhillon wrote:
> > > > > +static int sys_pidfd_getfd(int pidfd, int fd, int flags)
> > > > > +{
> > > > > + return syscall(__NR_pidfd_getfd, pidfd, fd, flags);
> > > > > +}
> > > >
> > > > I think you can move this to the pidfd.h header as:
> > > >
> > > > static inline int sys_pidfd_getfd(int pidfd, int fd, int flags)
> > > > {
> > > > return syscall(__NR_pidfd_getfd, pidfd, fd, flags);
> > > > }
> > > >
> > > > Note, this also needs an
> > > >
> > > > #ifndef __NR_pidfd_getfd
> > > > __NR_pidfd_getfd -1
> > > > #endif
> > > > so that compilation doesn't fail.
> > > >
> > > I'll go ahead and move this into pidfd.h, and follow the pattern there. I
> > > don't think it's worth checking if each time the return code is ENOSYS.
> > >
> > > Does it make sense to add something like:
> > > #ifdef __NR_pidfd_getfd
> > > TEST_HARNESS_MAIN
> > > #else
> > > int main(void)
> > > {
> > > fprintf(stderr, "pidfd_getfd syscall not supported\n");
> > > return KSFT_SKIP;
> > > }
> > > #endif
> > >
> > > to short-circuit the entire test suite?
> >
> > You mean the getfd testsuite? If so and that works, then sounds like a
> > good idea to me.
> >
> > >
> > >
> > >
> > > >
> > > > Hm, isn't it safer to do 65535 explicitly? Since USHRT_MAX can
> > > > technically be greater than 65535.
> > > >
> > > I borrowed this from the BPF tests. I can hardcode something like:
> > > #define NOBODY_UID 65535
> > > and setuid to that, if you think it's safer?
> >
> > If you want to specifically seteuid() to 65535 then yes, using the
> > hard-coded number or using a dedicated macro seems better.
> >
> > >
> > > > > +
> > > > > + ASSERT_EQ(1, send(self->sk, "P", 1, 0));
> > > > > + ASSERT_EQ(1, recv(self->sk, &c, 1, 0));
> > > > > +
> > > > > + fd = sys_pidfd_getfd(self->pidfd, self->remote_fd, 0);
> > > > > + EXPECT_EQ(-1, fd);
> > > > > + EXPECT_EQ(EPERM, errno);
> > > > > +
> > > > > + if (uid == 0)
> > > > > + ASSERT_EQ(0, seteuid(0));
> > > > > +}
> > > > > +
> > > > > +TEST_F(child, fetch_fd)
> > > > > +{
> > > > > + int fd, ret;
> > > > > +
> > > > > + fd = sys_pidfd_getfd(self->pidfd, self->remote_fd, 0);
> > > > > + ASSERT_GE(fd, 0);
> > > > > +
> > > > > + EXPECT_EQ(0, sys_kcmp(getpid(), self->pid, KCMP_FILE, fd, self->remote_fd));
> > > >
> > > > So most of these tests seem to take place when the child has already
> > > > called exit() - or at least it's very likely that the child has already
> > > > called exit() - and remains a zombie. That's not ideal because
> > > > that's not the common scenario/use-case. Usually the task of which we
> > > > want to get an fd will be alive. Also, if the child has already called
> > > > exit(), by the time it returns to userspace it should have already
> > > > called exit_files() and so I wonder whether this test would fail if it's
> > > > run after the child has exited. Maybe I'm missing something here... Is
> > > > there some ordering enforced by TEST_F()?
> > > Yeah, I think perhaps I was being too clever.
> > > The timeline roughly goes something like this:
> > >
> > > # Fixture bringup
> > > [parent] creates socket_pair
> > > [parent] forks, and passes pair down to child
> > > [parent] waits to read sizeof(int) from the sk_pair
> > > [child] creates memfd
> > > [__child] sends local memfd number to parent via sk_pair
> > > [__child] waits to read from sk_pair
> > > [parent] reads remote memfd number from socket
> > > # Test
> > > [parent] performs tests
> > > # Fixture teardown
> > > [parent] closes sk_pair
> > > [__child] reads 0 from recv on sk_pair, implies the other end is closed
> > > [__child] Returns / exits 0
> > > [parent] Reaps child / reads exit code
> > >
> > > ---
> > > The one case where this is not true, is if the parent sends 'P' to the sk pair,
> > > it triggers setting PR_SET_DUMPABLE to 0, and then resumes waiting for the fd to
> > > close.
> > >
> > > Maybe I'm being too clever? Instead, the alternative was to send explicit stop /
> > > start messages across the sk_pair, but that got kind of ugly. Do you have a
> > > better suggestion?
> >
> > If I understand correctly you just need to block the child to stop it
> > from exiting. Couldn't you do this by simply calling recv() on the
> > socket in the child thereby blocking it? At the end you just send a
> > final message to proceed and if that doesn't work SIGKILL it?
> >
> This already exists in:
> while ((ret = recv(sk, &buf, sizeof(buf), 0)) > 0) {
> if (buf == 'P') {
> ret = prctl(PR_SET_DUMPABLE, 0);
> if (ret < 0) {
> fprintf(stderr,
> "%s: Child failed to disable ptrace\n",
> strerror(errno));
> return -1;
> }
> } else {
> fprintf(stderr, "Child received unknown command %c\n",
> buf);
> return -1;
> }
> ret = send(sk, &buf, sizeof(buf), 0);
> if (ret != 1) {
> fprintf(stderr, "%s: Child failed to ack\n",
> strerror(errno));
> return -1;
> }
> }
> ----
> This will block until the close(self->sk) in the fixture teardown. Then ret
> returns 0, and the child should exit. Maybe a comment like:
> /*
> * The fixture setup is completed at this point. The tests will run.
> *
> * Either we will read 'P' off of the sk, indicating that we need
> * to disable ptrace, or if the other side of the socket is closed
> * recv will return 0-bytes. This indicates that the fixture teardown
> * has occured, and the child should exit.
> */
> would be useful?

Ah yeah, that would be helpful. I missed that while reading the code. So
the child is definitely still alive when te tests are run, it seems.
That explains why this didn't fail. :)

>
> > >
> > > >
> > > > Also, what does self->pid point to? The fd of the already exited child?
> > > It's just the pid of the child. pidfd is the fd of the (unexited) child.
> I have no idea if it's pro / against the commenting style to blow up that
> structure:

I think it's fine to comment it like that.

Thanks!
Christian