LinuxQuestions.org
Latest LQ Deal: Latest LQ Deals
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices


Reply
  Search this Thread
Old 01-27-2011, 02:44 PM   #1
orgcandman
Member
 
Registered: May 2002
Location: new hampshire
Distribution: Fedora, RHEL
Posts: 600

Rep: Reputation: 109Reputation: 109
Re-mapping stdio for a fork'd/exec'd executable doesn't seem to behave


I have a monitoring program ( GIT link to sourceforge ) which I'm trying to use to track when a child exits/dies/whatever. I'm calling fork(), then close for 0,1,2, and then opening /dev/null, monitored.stdout, and monitored.stderr as a replacement. I'm not sure if I've done something incorrect (perhaps I should use dup2 for explicit assignment?) but it appears that printf() messages are just being blackholed. I've tried setting the line buffering as a last ditch effort.

On a different system, using code similar to the spawn_monitor() function, this appears to work fine, which makes me think I'm relying on some implementation specific detail.

Relevant function, for those who don't click links:

Code:
  26 int spawn_monitored(char *argv[])
  27 {
  28     int exit = 0;
  29     pid_t child;
  30     const struct rlimit inf = {
  31         RLIM_INFINITY, RLIM_INFINITY };
  32 
  33     printf("[] Attempting to spawn a monitored task.\n");
  34     
  35     setrlimit(RLIMIT_CORE, &inf);
  36 
  37     switch(child = fork())
  38     {
  39     case 0:
  40         /*CHILD*/
  41         close(0); close(1); close(2);
  42         
  43         open("/dev/null", O_RDONLY);
  44         open("monitored.stdout", O_WRONLY|O_CREAT, S_IRUSR | S_IWUSR);
  45         open("monitored.stderr", O_WRONLY|O_CREAT, S_IRUSR | S_IWUSR);
  46         
  47         setlinebuf(stdout);
  48         setlinebuf(stderr);
  49 
  50         /* force core dumping */
  51         setrlimit(RLIMIT_CORE, &inf);
  52 
  53         execvp(argv[0], argv);
  54 
  55     case -1:
  56         fprintf(stderr, 
  57                 "[SFUZZ-ORACLE] There was an error attempting to spawn a "
  58                 "child.\n");
  59         fprintf(stderr,
  60                 "Debugging not available!\n");
  61         return -1;
  62 
  63     default:
  64         do
  65         {
  66             int istatus;
  67             pid_t status = waitpid(-1, &istatus, 0);
  68             if(status == -1) { exit = 1; 
  69                 fprintf
  70                     (stderr,
  71                      "[SFUZZ-ORACLE] Unable to obtain status. Attempting "
  72                      "to kill\n");
  73                 kill(child, SIGTERM); /* send sigterm */
  74                 return -1;
  75             } else
  76             {
  77                 if (WIFEXITED(istatus))
  78                 {
  79                     printf("[%u] Exited (possibly normal), status[%d]\n",
  80                            status, WEXITSTATUS(istatus));
  81                     exit = 1;
  82                 }
  83                 if (WIFSIGNALED(istatus))
  84                 {
  85                     printf("[%u] Terminated due to signal #%d [%s]. %s.\n",
  86                            status, WTERMSIG(istatus),
  87                            strsignal(WTERMSIG(istatus)),
  88 #ifdef WCOREDUMP
  89 # if WCOREDUMP
  90                            WCOREDUMP(istatus) ? "Core dumped" :
  91                            "No core available");
  92 # else
  93                            "Unable to check for core status");
  94 # endif /* WCOREDUMP */
  95 #else
  96                            "Unable to check for core status");
  97 #endif /* !defined WCOREDUMP */
  98 
  99                     exit = 1;
 100                 }
 101                 
 102                 if (WIFSTOPPED(istatus))
 103                 {
 104                     printf("[%u] Signaled to stop by #%d [%s].\n", status,
 105                            WSTOPSIG(istatus), strsignal(WSTOPSIG(istatus)));
 106                 }
 107                 if (WIFCONTINUED(istatus))
 108                 {
 109                     printf("[%u] Continued\n", status);
 110                 }
 111             }
 112         } while(!exit);
 113     }
 114 }
Anyone have any clues? My next step is to try dup2 (and I'll mark this solved if that works), and I'll report if that is successful.
 
Old 01-27-2011, 07:55 PM   #2
ta0kira
Senior Member
 
Registered: Sep 2004
Distribution: FreeBSD 9.1, Kubuntu 12.10
Posts: 3,078

Rep: Reputation: Disabled
dup2 is the standard way of doing it; as far as I know, POSIX standards assume that 0, 1, and 2 are always open. There is no reason to expect that open will use those descriptors if they aren't open. In fact, that might actually be a security risk.

Something I do, which you might not choose to do, is SIGSTOP the child, then give it a new process group in the parent and SIGCONT it. This allows you to monitor the subsequent forks of the child and chain the I/O of several forks together. All a matter of preference, I guess.
https://developer.berlios.de/snippet...ppet&id=100036
Kevin Barry

Last edited by ta0kira; 01-27-2011 at 08:02 PM.
 
Old 01-27-2011, 08:27 PM   #3
wje_lq
Member
 
Registered: Sep 2007
Location: Mariposa
Distribution: FreeBSD,Debian wheezy
Posts: 811

Rep: Reputation: 178Reputation: 178
Quote:
Originally Posted by ta0kira View Post
as far as I know, POSIX standards assume that 0, 1, and 2 are always open.
Citation needed. This is not true.
Quote:
Originally Posted by ta0kira View Post
There is no reason to expect that open will use those descriptors if they aren't open.
Yes, there is such a reason. The reason is that it's true. To quote W. Richard Stevens of happy memory, in chapter 3 of Advanced Programming in the UNIX Environment:
Quote:
The file descriptor returned by open is guaranteed to be the lowest numbered unused descriptor.
And from the man page for open(2) shipped with Debian Lenny (5.0.4):
Code:
                                             The file descriptor returned by a
       successful call will be the lowest-numbered file  descriptor  not  cur-
       rently open for the process.
Quote:
Originally Posted by ta0kira View Post
In fact, that might actually be a security risk.
Again, citation needed. There are lots of ways to introduce security risks, but I'm fairly sure this isn't one of them.
 
Old 01-28-2011, 12:35 AM   #4
ta0kira
Senior Member
 
Registered: Sep 2004
Distribution: FreeBSD 9.1, Kubuntu 12.10
Posts: 3,078

Rep: Reputation: Disabled
Quote:
Originally Posted by wje_lq View Post
Citation needed. This is not true.
I was mistakenly thinking of what one should expect if execv is called and 0, 1, or 2 are closed, and my logic followed from that. The key flaw being that execv isn't called. Sorry! I don't recall where I read it, but I'm pretty sure if execv is called and a standard descriptor is closed you shouldn't expect the descriptor to be available in the subsequent process.
Kevin Barry

PS My security risk comment was along the same lines; I was imagining a new process opening a socket and inadvertently either reading stdin from it or writing stdout to it.

Last edited by ta0kira; 01-28-2011 at 12:43 AM.
 
Old 01-28-2011, 06:26 AM   #5
wje_lq
Member
 
Registered: Sep 2007
Location: Mariposa
Distribution: FreeBSD,Debian wheezy
Posts: 811

Rep: Reputation: 178Reputation: 178
Quote:
Originally Posted by ta0kira View Post
I was mistakenly thinking of what one should expect if execv is called and 0, 1, or 2 are closed, and my logic followed from that.
For each file descriptor, there's a "close on exec" flag. If it's on, then exec'ing something new will close that file descriptor. But it's off by default.
Quote:
Originally Posted by ta0kira View Post
The key flaw being that execv isn't called.
Actually, it turns out that he does something very close to this on line 53 of the code he posted: he calls execvp. And that actually poses a security risk if his PATH environment variable includes "." before the others. That security risk, however, is orthogonal to anything he does with file descriptors
Quote:
Originally Posted by ta0kira View Post
I'm pretty sure if execv is called and a standard descriptor is closed you shouldn't expect the descriptor to be available in the subsequent process.
Leave out the word "standard", and I agree with you, if by "available" you mean "already open". And he does actually cripple stdin; it will yield end of file upon any input. That could be by design, and there's nothing inherently wrong with that. It has the following advantage over just leaving stdin closed:
Quote:
Originally Posted by ta0kira View Post
My security risk comment was along the same lines; I was imagining a new process opening a socket and inadvertently either reading stdin from it or writing stdout to it.

Last edited by wje_lq; 01-28-2011 at 07:09 AM. Reason: minor detail correction
 
Old 01-28-2011, 07:07 AM   #6
wje_lq
Member
 
Registered: Sep 2007
Location: Mariposa
Distribution: FreeBSD,Debian wheezy
Posts: 811

Rep: Reputation: 178Reputation: 178
Quote:
Originally Posted by orgcandman View Post
it appears that printf() messages are just being blackholed.
I played with your posted function a bit.

In the program that emits the printf() messages, do an fflush() after each printf(). Otherwise, the messages won't appear until either there's lots of data or stdout is closed.
 
Old 01-28-2011, 09:30 AM   #7
orgcandman
Member
 
Registered: May 2002
Location: new hampshire
Distribution: Fedora, RHEL
Posts: 600

Original Poster
Rep: Reputation: 109Reputation: 109
Quote:
Originally Posted by wje_lq View Post
... do an fflush() after each printf().
I was hoping to avoid having this as a requirement. The reason is simple - this piece is meant to monitor the application and report 'taint' and 'crash' information when such events occur. I'm hopeful that I can get it to work without having to modify the monitored application (which may sometime be a difficult proposition at best).

dup2 didn't work to do what I wanted - it's not behaving in a manner I'd prefer. There must be a way to do this, but I'm not sure (not really an interface guy).

Thanks for the responses.
 
Old 01-28-2011, 12:03 PM   #8
ta0kira
Senior Member
 
Registered: Sep 2004
Distribution: FreeBSD 9.1, Kubuntu 12.10
Posts: 3,078

Rep: Reputation: Disabled
Quote:
Originally Posted by wje_lq View Post
For each file descriptor, there's a "close on exec" flag. If it's on, then exec'ing something new will close that file descriptor. But it's off by default.

Actually, it turns out that he does something very close to this on line 53 of the code he posted: he calls execvp. And that actually poses a security risk if his PATH environment variable includes "." before the others. That security risk, however, is orthogonal to anything he does with file descriptors
I was unclear. I saw that the lines in question were right after fork and I failed to notice execvp. I meant that if 0, 1, or 2 are closed, then execv is called, then the new process calls open, one should not expect the new descriptor to be 0, 1, or 2; it's equivalent to undefined behavior in C++.

With that in mind, imagine closing 0 and calling tee -a file where "file" is non-empty. Provided "file" is the first thing opened by tee and 0 is still unused, this could expand "file" to fill the whole filesystem => security risk.
Kevin Barry
 
Old 01-28-2011, 01:29 PM   #9
orgcandman
Member
 
Registered: May 2002
Location: new hampshire
Distribution: Fedora, RHEL
Posts: 600

Original Poster
Rep: Reputation: 109Reputation: 109
Quote:
Originally Posted by ta0kira View Post
With that in mind, imagine closing 0 and calling tee -a file where "file" is non-empty. Provided "file" is the first thing opened by tee and 0 is still unused, this could expand "file" to fill the whole filesystem => security risk.
Kevin Barry
While the discussion is orthogonal, I don't believe this is correct. Most implementations of tee call an open of either write only or write+append only. Attempting to read (at least on my system) results in 0 bytes being read (and also deleting the contents in the case of write-only). I don't believe there is an explicit security issue here. The standards are a bit more ambiguous, and according to my reading, what you're describing is implementation dependent.

Any real C89/C99 gurus out there?
 
Old 01-28-2011, 01:37 PM   #10
wje_lq
Member
 
Registered: Sep 2007
Location: Mariposa
Distribution: FreeBSD,Debian wheezy
Posts: 811

Rep: Reputation: 178Reputation: 178
Quote:
Originally Posted by ta0kira View Post
if 0, 1, or 2 are closed, then execv is called, then the new process calls open, one should not expect the new descriptor to be 0, 1, or 2; it's equivalent to undefined behavior in C++.
Oh, yes, one should. According to the man page, one should. open() uses the first unused file descriptor.

His only security problem here is that he should check the open() for success before continuing.

Oh, and say, orgcandman: Have you tried removing this:
Code:
setlinebuf(stdout);
and replacing it with this?
Code:
setvbuf(stdout,NULL,_IONBF,0);
That will probably fix your original problem.
 
Old 01-28-2011, 02:03 PM   #11
wje_lq
Member
 
Registered: Sep 2007
Location: Mariposa
Distribution: FreeBSD,Debian wheezy
Posts: 811

Rep: Reputation: 178Reputation: 178
Quote:
Originally Posted by orgcandman View Post
The standards are a bit more ambiguous, and according to my reading, what you're describing is implementation dependent.
The only thing that the ANSI 98/99 standard has to say about fopen() is this. Sorry for the sloppy formatting.
Quote:
7.19.5.3 The fopen function
Synopsis
1 #include <stdio.h>
FILE *fopen(const char * restrict filename,
const char * restrict mode);
Description
2 The fopen function opens the file whose name is the string pointed to by filename,
and associates a stream with it.
3 The argument mode points to a string. If the string is one of the following, the file is
open in the indicated mode. Otherwise, the behavior is undefined.228)
r open text file for reading
w truncate to zero length or create text file for writing
a append; open or create text file for writing at end-of-file
rb open binary file for reading
wb truncate to zero length or create binary file for writing
ab append; open or create binary file for writing at end-of-file
r+ open text file for update (reading and writing)
w+ truncate to zero length or create text file for update
a+ append; open or create text file for update, writing at end-of-file
r+b or rb+ open binary file for update (reading and writing)
w+b or wb+ truncate to zero length or create binary file for update
a+b or ab+ append; open or create binary file for update, writing at end-of-file
4 Opening a file with read mode ('r' as the first character in the mode argument) fails if
the file does not exist or cannot be read.
5 Opening a file with append mode ('a' as the first character in the mode argument)
causes all subsequent writes to the file to be forced to the then current end-of-file,
regardless of intervening calls to the fseek function. In some implementations, opening
a binary file with append mode ('b' as the second or third character in the above list of
mode argument values) may initially position the file position indicator for the stream
beyond the last data written, because of null character padding.
6 When a file is opened with update mode ('+' as the second or third character in the
above list of mode argument values), both input and output may be performed on the
associated stream. However, output shall not be directly followed by input without an
intervening call to the fflush function or to a file positioning function (fseek,
fsetpos, or rewind), and input shall not be directly followed by output without an
intervening call to a file positioning function, unless the input operation encounters end-
of-file. Opening (or creating) a text file with update mode may instead open (or create) a
binary stream in some implementations.
7 When opened, a stream is fully buffered if and only if it can be determined not to refer to
an interactive device. The error and end-of-file indicators for the stream are cleared.
Returns
8 The fopen function returns a pointer to the object controlling the stream. If the open
operation fails, fopen returns a null pointer.

228) If the string begins with one of the above sequences, the implementation might choose to ignore the
remaining characters, or it might use them to select different kinds of a file (some of which might not
conform to the properties in 7.19.2).
 
Old 01-28-2011, 02:11 PM   #12
orgcandman
Member
 
Registered: May 2002
Location: new hampshire
Distribution: Fedora, RHEL
Posts: 600

Original Poster
Rep: Reputation: 109Reputation: 109
Quote:
Originally Posted by wje_lq View Post
Oh, and say, orgcandman: Have you tried removing this:
Code:
setlinebuf(stdout);
and replacing it with this?
Code:
setvbuf(stdout,NULL,_IONBF,0);
That will probably fix your original problem.
I have. No success. It seems that I'll have to live with this limitation (so far, anyway).
 
Old 01-28-2011, 06:11 PM   #13
wje_lq
Member
 
Registered: Sep 2007
Location: Mariposa
Distribution: FreeBSD,Debian wheezy
Posts: 811

Rep: Reputation: 178Reputation: 178
Quote:
Originally Posted by orgcandman View Post
I have. No success. It seems that I'll have to live with this limitation (so far, anyway).
And I think I've figured out why that doesn't work. It specifies how you want the buffering to be handled. exec a new program? Blammo! Memory is entirely rearranged, and so all the standard I/O buffering is reset.

I can think of two ways out.

First, I know you said that modifying the application is difficult. If the reason is that it's difficult to modify (or even find!) all the printf statements, all you need to do is place the setvbuf call at the beginning of the main program in the application.

But if modifying the application at all is truly difficult, then monitor any output within function spawn_monitored() itself. You'll be using pseudoteletypes, select(), and a signal telling you when the child is finished.
 
Old 01-28-2011, 06:51 PM   #14
ta0kira
Senior Member
 
Registered: Sep 2004
Distribution: FreeBSD 9.1, Kubuntu 12.10
Posts: 3,078

Rep: Reputation: Disabled
Quote:
Originally Posted by wje_lq View Post
Oh, yes, one should. According to the man page, one should. open() uses the first unused file descriptor.
Your mistake is in the wording "first unused file descriptor". See the Open Group page for execv:
Quote:
Applications should not depend on file descriptors 0, 1, and 2 being closed after an exec. A future version may allow these file descriptors to be automatically opened for any process.
Regarding the Linux system call execve:
Quote:
As a general principle, no portable program, whether privileged or not, can assume that [0, 1, and 2] will remain closed across an execve().
Certainly if 0, 1, or 2 are closed after execv, one should expect those to be filled in first when open is called. And I agree in advance that this doesn't pertain to OP's issue; I'm merely backing up my previous claim, which was made under the wrong assumptions.
Kevin Barry
 
1 members found this post helpful.
Old 01-28-2011, 07:24 PM   #15
wje_lq
Member
 
Registered: Sep 2007
Location: Mariposa
Distribution: FreeBSD,Debian wheezy
Posts: 811

Rep: Reputation: 178Reputation: 178
You linked to a very useful site of which I was unaware. Thank you.
 
  


Reply


Thread Tools Search this Thread
Search this Thread:

Advanced Search

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off



Similar Threads
Thread Thread Starter Forum Replies Last Post
stdio.c cigarstub Programming 12 12-19-2014 06:39 AM
How can I get FireFox to behave? Josh000 Linux - Desktop 13 04-27-2010 06:58 AM
Trying to get GRUB to behave sonicsmooth Linux - Hardware 1 05-21-2009 09:04 PM
VI dosnt behave .... phoeniXflame Linux - Software 7 06-22-2003 11:24 AM
physical scsi channel mapping to scsiX device node mapping, how to configure manually drthornt Linux - Hardware 3 02-09-2003 11:50 AM

LinuxQuestions.org > Forums > Non-*NIX Forums > Programming

All times are GMT -5. The time now is 01:34 PM.

Main Menu
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Facebook: linuxquestions Google+: linuxquestions
Open Source Consulting | Domain Registration