Bug#607844: weird interaction between setsid and SIGTSTP

December 22nd, 2010 - 05:30 pm ET by Kees Cook | Report spam

Package: sbuild
Version: 0.60.5-1
Severity: normal
User: ubuntu-devel@lists.ubuntu.com
Usertags: origin-ubuntu natty

While trying to build "upstart" in Ubuntu using the latest sbuild, one of
the unit tests failed. This was reduced to a test of "raise(SIGTSTP)",
which wasn't actually stopping the process. In tracking this down, I
isolated it as far as setting SETSID to 1 during the Sbuild::Build phase.

Attached is "wstopped.c" and "sbuild-poc" which tries to isolate the
problem. With SETSID=0, I get the expected behavior; the process waits for
SIGCONT:

# perl /tmp/sbuild-poc /tmp/wstopped 0
ok

With SETSID=1 (last cmdline option), the process does not wait for SIGCONT:

# perl /tmp/sbuild-poc /tmp/wstopped 1
waitid: No child processes

The bulk of wstopped.c is the creation of IPC pipes to make sure the child
has started and the parent can wait on it.

Very weird. Do you have any idea what is going on here?

Thanks!

-Kees

Kees Cook @debian.org


#include <sys/types.h>
#include <stdlib.h>
#include <sys/wait.h>

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>


void
test (void)
{
int fds[2];
if (pipe (fds) < 0) {
perror ("pipe");
exit (1);
}

pid_t pid = fork ();
if (pid < 0) {
perror ("fork");
exit (1);

} else if (pid == 0) {
close (fds[0]);
if (write (fds[1], "", 1) < 1) {
perror ("write");
exit (1);
}
close (fds[1]);

raise (SIGTSTP);
exit (0);

} else if (pid > 0) {
char buf[1];
close (fds[1]);
if (read (fds[0], buf, 1) < 1) {
perror ("read");
exit (1);
}
close (fds[0]);

siginfo_t info;
if (waitid (P_PID, pid, &info, WSTOPPED | WNOWAIT) != 0) {
perror ("waitid");
exit (1);
}

waitid (P_PID, pid, &info, WSTOPPED);

if (kill (pid, SIGCONT) < 0) {
perror ("kill");
exit (1);
}

int status;
waitpid (pid, &status, 0);
}
}

int
main (int argc, char *argv[])
{
test ();
puts("ok");

return 0;
}



#!/usr/bin/perl
# Author: Kees Cook <kees@ubuntu.com>
#
# This shows a strange failure with raise(SIGTSTP) when SETSID=1 during
# an sbuild run.

# Build up a package similar to Sbuild::Build
package Test::Build;
use strict;
use warnings;
use Sbuild::Base;
use Sbuild::ChrootRoot;
use Sbuild::Conf;
use Sbuild::Utility;

BEGIN {
use Exporter ();
our (@ISA, @EXPORT);

@ISA = qw(Exporter Sbuild::Base);

@EXPORT = qw();
}

sub new {
my $class = shift;

my $conf = Sbuild::Conf->new();

my $self = $class->SUPER::new($conf);
bless($self, $class);

return $self;
}

sub build {
my $self = shift;
my $cmd = shift;
my $setsid = shift;
$setsid += 0;

my $session = Sbuild::ChrootRoot->new($self->get('Config'));

$session->begin_session();
$self->set('Session', $session);

my $buildenv = {};
$buildenv->{'PATH'} = '/bin:/usr/bin';

my $command = {
COMMAND => [$cmd],
ENV => $buildenv,
SETSID => $setsid,
CHROOT => 1,
PRIORITY => 0,
DIR => '/tmp'
};

my $pipe = $self->get('Session')->pipe_command($command);

while(<$pipe>) {
$self->log($_);
}
close($pipe);
}

package main;

my $test = Test::Build->new();
$test->build($ARGV[0], $ARGV[1]);




To UNSUBSCRIBE, email to debian-bugs-dist-REQUEST@lists.debian.org
with a subject of "unsubscribe". Trouble? Contact listmaster@lists.debian.org
email Follow the discussionReplies 1 replyReplies Make a reply

Replies

#1 Roger Leigh
December 29th, 2010 - 11:00 am ET | Report spam

On Wed, Dec 22, 2010 at 02:17:45PM -0800, Kees Cook wrote:
While trying to build "upstart" in Ubuntu using the latest sbuild, one of
the unit tests failed. This was reduced to a test of "raise(SIGTSTP)",
which wasn't actually stopping the process. In tracking this down, I
isolated it as far as setting SETSID to 1 during the Sbuild::Build phase.

Attached is "wstopped.c" and "sbuild-poc" which tries to isolate the
problem. With SETSID=0, I get the expected behavior; the process waits for
SIGCONT:

# perl /tmp/sbuild-poc /tmp/wstopped 0
ok

With SETSID=1 (last cmdline option), the process does not wait for SIGCONT:

# perl /tmp/sbuild-poc /tmp/wstopped 1
waitid: No child processes

The bulk of wstopped.c is the creation of IPC pipes to make sure the child
has started and the parent can wait on it.

Very weird. Do you have any idea what is going on here?



Yes, but my understanding may be incomplete…

After the setsid(2) call, the dpkg-buildpackage command is run in its
own separate session, with dpkg-buildpackage being the process group
leader. A side-effect of setsid is that the process group has no
controlling TTY. A consequence of having no CTTY is that the terminal
process control signals such as SIGTSTP (and also SIGTTIN/TTOU) won't
work--there's no tty to signal the group leader [though I admit I don't
know what happens when there's no TTY; apparently nothing from what
you've described].

This change was introduced in commit 8f254221. However, setsid was
used routinely until removed in c4a3bd69. There was a reason for
having the setsid call, though it's something I've never been totally
happy about. It's so we can terminate the entire dpkg-buildpackage
process group without killing ourselves (since otherwise we're in the
same group and cleanup is messy--we can kill dpkg-buildpackage but not
any of its children, so the build can continue on unless we kill the
entire group as a shell would). Killing the entire process group in
a single shot is much more robust.

From the upstart perspective, it's lacking a ctty. However, having a
controlling tty is not something we have ever guaranteed; we don't
even guarantee having a terminal (since stdin/out are all redirected
to /dev/null or logfiles--no interactivity is allowed).

Suggestions:
- upstart should check if it has a controlling terminal. ttyname(2)
will return NULL if none is present. IMHO, upstart should work
in the absence of a controlling terminal; if it needs one for the
unit tests, it could either skip those tests or (better) create a
pty to run the tests on which would be much more robust all around
since you have full control over what's running on it.

- sbuild /could/ run the build connected to a pseudo-TTY. The build
can run on the pty slave and we can control the master for logging
etc. However, allowing terminal signals means builds can raise
signals and freeze a build indefinitely; this is also a reason why
we don't have a ctty right now--it means the buildds need less
manual cleanup if a build stops itself. I've considered doing this
several times in the past, but I'm yet to be convinced it adds any
useful value.


Regards,
Roger

.''`. Roger Leigh
: :' : Debian GNU/Linux http://people.debian.org/~rleigh/
`. `' Printing on GNU/Linux? http://gutenprint.sourceforge.net/
`- GPG Public Key: 0x25BFB848 Please GPG sign your mail.






To UNSUBSCRIBE, email to
with a subject of "unsubscribe". Trouble? Contact

Similar topics