Bug 23151 - A forked process with unclosed file does lseek before exit and can cause infinite loop in parent I/O
Summary: A forked process with unclosed file does lseek before exit and can cause infi...
Status: RESOLVED INVALID
Alias: None
Product: glibc
Classification: Unclassified
Component: stdio (show other bugs)
Version: unspecified
: P2 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2018-05-09 06:12 UTC by jonathan.leffler
Modified: 2018-05-12 09:31 UTC (History)
1 user (show)

See Also:
Host:
Target:
Build:
Last reconfirmed:
fweimer: security-


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description jonathan.leffler 2018-05-09 06:12:02 UTC
See Stack Overflow Q&A:
* https://stackoverflow.com/questions/50110992/why-does-forking-my-process-cause-the-file-to-be-read-infinitely
* https://stackoverflow.com/questions/50244579/unwanted-child-processes-being-created-while-file-reading

As the accepted answer to the "why does forking my process" question shows, when the child process exits, it seeks on the file descriptor towards the beginning of the file, thus changing the position of the read position in the (shared) open file description, potentially leading to an infinite loop in the parent process as it never encounters EOF.

The output from strace (strace -ff -o st-out -- neof97) for the children shows:

$ cat st-out.80833
lseek(0, -63, SEEK_CUR)                 = 21
exit_group(0)                           = ?
+++ exited with 0 +++
$ cat st-out.80834
lseek(0, -42, SEEK_CUR)                 = -1 EINVAL (Invalid argument)
exit_group(0)                           = ?
+++ exited with 0 +++
$ cat st-out.80835
lseek(0, -21, SEEK_CUR)                 = 0
exit_group(0)                           = ?
+++ exited with 0 +++
$ cat st-out.80836
exit_group(0)                           = ?
+++ exited with 0 +++
$


One version of the code that shows the problem is (neof97.c).  It limits the 'infinite input' to 30 lines.

#include "posixver.h"
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

enum { MAX = 100 };

int main(void)
{
    if (freopen("input.txt", "r", stdin) == 0)
        return 1;
    char s[MAX];
    for (int i = 0; i < 30 && fgets(s, MAX, stdin) != NULL; i++)
    {
        // Commenting out this region fixes the issue
        int status;
        pid_t pid = fork();
        if (pid == 0)
        {
            exit(0);
        }
        else
        {
            waitpid(pid, &status, 0);
        }
        // End region
        printf("%s", s);
    }
    return 0;
}

The file input.txt can be any text; 4 lines of 20 random characters each is sufficient to show the bug.

If the file is explicitly closed in the child, the lseek() does not occur which avoids the problem.

The problem was reproduced in a Ubuntu 16.04 LTS system running as a VM under VMWare Fusion on a MacBook Pro running macOS 10.13.4 (High Sierra).  When the same code was recompiled and run on the macOS host operating system, the problem was not exhibited.
Comment 1 Andreas Schwab 2018-05-09 08:16:41 UTC
Please read <http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_05_01>, especially this paragraph:

Note that after a fork(), two handles exist where one existed before. The application shall ensure that, if both handles can ever be accessed, they are both in a state where the other could become the active handle first. The application shall prepare for a fork() exactly as if it were a change of active handle. (If the only action performed by one of the processes is one of the exec functions or _exit() (not exit()), the handle is never accessed in that process.)