ash while loops and variable scope

Denys Vlasenko vda.linux at googlemail.com
Sun Sep 5 13:29:28 UTC 2010


On Sun, Sep 5, 2010 at 5:33 AM, Harald Becker <ralda at gmx.de> wrote:
>  Hallo Denys!
>> Newer bash seems to use /proc/PID/fd/NNN. Need to investigate hot it does that.
> That don't need to be investigated and isn't difficult to do:
>
> - use open() or creat() to create a file or maybe pipe()/fork() to run a
> child process
> - in case a file is created temporarily do an unlink() while still
> holding the file open
> - use sprintf( "/proc/%u/fd/%d", getpid(), your_open_fd )

This requires writable filesystem. I don't like it when some features
of the shell depend in whether I have a writable filesystem somewhere,

> That's it. The file still exists on file system until all references to
> it's file descriptor got closed and is automatically removed afterwards.

But this leaks a file descriptor.

The code itself is not that hard. bash does something like this:

int main() {
        char buf[64];
        int pipefd[2];
        pipe(pipefd);
        if (fork() == 0) {
                /* child */
                close(pipefd[0]);
                if (pipefd[1] != 63) {
                        dup2(pipefd[1], 63);
                        close(pipefd[1]);
                }
                /* bash would exec here, passing "/proc/self/fd/63" to
the child,
                 * child will open it and write into it. */
                write(open("/proc/self/fd/63", /*O_WRONLY:*/ 1), "hello\n", 6);
                return 0;
        }
        close(pipefd[1]);
        write(1, buf, read(pipefd[0], buf, sizeof(buf)));
        return 0;
}

The strace:

9567  pipe([3, 4])                      = 0
9567  clone(child_stack=0,
flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD,
child_tidptr=0x7fc00c3e19d0) = 9568
9567  close(4)                          = 0
9567  read(3,  <unfinished ...>
9568  close(3)                          = 0
9568  dup2(4, 63)                       = 63
9568  close(4)                          = 0
9568  open("/proc/self/fd/63", O_WRONLY) = 3
9568  write(3, "hello\n", 6 <unfinished ...>
9567  <... read resumed> "hello\n", 64) = 6
9567  write(1, "hello\n", 6)            = 6
9567  exit_group(0)                     = ?
9568  <... write resumed> )             = 6
9568  exit_group(0)                     = ?

See? fd 63 in the child LEAKS. Child doesn't realize it has it open.
If child will decide to close the "file" it opened,
fd 63 will still be open, and the reading process will not see the EOF!

Modify the program like this and you'll see:

...
...
                /* bash would exec here, passing "/proc/self/fd/63" to
the child,
                 * child will open it and write into it. */
                int fd = open("/proc/self/fd/63", /*O_WRONLY:*/ 1);
                if (fd != 1) {
                        dup2(fd, 1);
                        close(fd);
                }
                write(1, "hello\n", 6);
                close(1); /* send EOF */
                sleep(3);
                return 0;
        }
        close(pipefd[1]);
        write(1, buf, read(pipefd[0], buf, sizeof(buf)));
        write(1, buf, read(pipefd[0], buf, sizeof(buf)));
        return 0;
}

The second read() in the parent will not return 0 (EOF) at once,
it will wait 3 seconds for child to exit.

This is wrong.

Here's the strace:

9604  15:25:01.302013 pipe([3, 4])      = 0
9604  15:25:01.302143 clone(child_stack=0,
flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD,
child_tidptr=0x7f94a12d59d0) = 9605
9604  15:25:01.302413 close(4)          = 0
9604  15:25:01.302615 read(3,  <unfinished ...>
9605  15:25:01.302851 close(3)          = 0
9605  15:25:01.303017 dup2(4, 63)       = 63
9605  15:25:01.303166 close(4)          = 0
9605  15:25:01.303314 open("/proc/self/fd/63", O_WRONLY) = 3
9605  15:25:01.303504 dup2(3, 1)        = 1
9605  15:25:01.303665 close(3)          = 0
9605  15:25:01.303833 write(1, "hello\n", 6 <unfinished ...>
9604  15:25:01.303949 <... read resumed> "hello\n", 64) = 6
9604  15:25:01.304018 write(1, "hello\n", 6) = 6
9604  15:25:01.304228 read(3,  <unfinished ...>
9605  15:25:01.304339 <... write resumed> ) = 6
9605  15:25:01.304395 close(1)          = 0
  --- parent will not see EOF despite us closing our output ---
9605  15:25:01.304506 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
9605  15:25:01.304632 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
9605  15:25:01.304764 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
9605  15:25:01.304931 nanosleep({3, 0}, 0x7fff48f8d0b0) = 0
  --- note 3 second gap here ---
9605  15:25:04.305315 exit_group(0)     = ?
9604  15:25:04.305527 <... read resumed> "", 64) = 0
9604  15:25:04.305653 --- SIGCHLD (Child exited) @ 0 (0) ---
9604  15:25:04.305741 write(1, "", 0)   = 0
9604  15:25:04.305930 exit_group(0)     = ?

-- 
vda


More information about the busybox mailing list