[comp.unix.wizards] Writing to A NON-Existing File in \"C\"

mchinni@ardec.arpa (Michael J. Chinni, SMCAR-CCS-E) (04/07/88)

In a message from Michael Deutsch  <deutsch@jplgodo.uucp> 
dated 6 Apr 88 03:47:17 GMT he writes:

 > I have a "C" program that records the program
 > results into a file, provided that file already
 > exists.  In case the file DOES NOT exist I want
 > the program to function identically but the results
 > should be flushed down the tube, i.e. nowhere, i.e.
 > written to a non-existing file?
 > 
 > What sort of "file pointer" or trick should I use
 > to accomplish my goal?
I have a couple of suggestions.  First, try writing to "/dev/null".
Second, a temporary file could be used ("mkstemp" in BSD or "tmpnam"/"tempnam"
and "tmpfile" in SysV).  Last, a filename of personal choice could be created,
written to, and then deleted at the end of the program.

	Mike Chinni
        US Army Armament Research, Development, and Engineering Center
        Picatinny Arsenal, New Jersey  07806-5000
        AUTOVON: 880-4140
        MILNET:  <mchinni@ardec>
        COMMERCIAL: (201) 724-4140

------
Standard Disclaimer
------

anamaria@lll-lcc.llnl.gov (Ana Maria De Alvare) (04/08/88)

I will also suggest to write it to stdout or use stderr.
They are there to be used on those instances.

Ana Maria De Alvare'
LLNL

drears@ardec.arpa (Dennis G. Rears (FSAC)) (04/08/88)

Michael Deutsch  writes:

->  I have a "C" program that records the program
->  results into a file, provided that file already
->  exists.  In case the file DOES NOT exist I want
->  the program to function identically but the results
->  should be flushed down the tube, i.e. nowhere, i.e.
->  written to a non-existing file?
->  
->  What sort of "file pointer" or trick should I use
->  to accomplish my goal?

Here is sample code that will allow you to do this. It will exit
if it can't open for appending any file.  If it can not stat the
file it assumes it doesn't exist.  You can be fancy and check errno
though.
----------------------------------------------------------------------

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>

#define FILENAME  "/usr/foobar"      /*  not a trademark of AT&T *?
#define NULL	  "/dev/null/"

main(a,b,c)
int a;
char *b[],*c[];

{
	struct stat *buf;
	FILE	*fopen(), *fp, *fr;

	if(!stat(FILE,buf){
		if ( (fp=fopen(NULL,"a")) == NULL ) { 
			/* or use perror() */
			(void)fprintf (stderr, 
				"%s: Can not open /dev/null file\n",b[0]));
			exit(-1);
		}
		else
			if ( (fp=fopen(FILENAME,"a")) == NULL ) { 
				(void)fprintf (stderr, 
				"%s: Can not open %sfile\n",b[0],FILENAME));
				exit(-1);
				
}


Dennis

------------------------------------------------------------
ARPA:		drears@ardec-ac4.arpa
UUCP:   	...!uunet!ardec-ac4.arpa!drears
AT&T:		201-724-6639
Snailmail:	Box 210, Wharton, NJ 07885
Govt Nonmail:	US Army ARDEC, ATTN SMCAR-FSS-E, Dennis Rears
		Bldg 94, Picatinny Arsenal, NJ 07806
Flames:		/dev/null
------------------------------------------------------------

mlandau@bbn.com (Matt Landau) (04/08/88)

In comp.unix.wizards (<12860@brl-adm.ARPA>), drears@ardec.arpa (Dennis G. Rears 
(FSAC)) writes:
>Here is sample code that will allow you to do this. It will exit
>if it can't open for appending any file.  If it can not stat the
>file it assumes it doesn't exist.  You can be fancy and check errno
>though.

>#define NULL	  "/dev/null/"

Probably want to pick another name for /dev/null, tho... :-)
--
 Matt Landau		    The happiest cold and lonely guy 
 mlandau@bbn.com	          stuck in the Yukon without a dog.

gandalf@csli.STANFORD.EDU (Juergen Wagner) (04/08/88)

Why all these lengthy solutions? As I tried to e-mail (host unreachable),
you could just do
	
# define do_output_p(file)	(!access((file),02))

and assign the result to a variable, say

static int do_output_flag;

main(...)
...
{
	char *file;
	...

	...
	do_output_flag = do_output_p(file);
	...
}

Then, to access the file use

# define Fopen      if (do_output_flag) fopen
# define Printf     if (do_output_flag) printf
# define Fclose     if (do_output_flag) fclose

Of course, another alternative would be just to open "/dev/null" instead of
the file specified.

Juergen "Gandalf" Wagner,		   gandalf@csli.stanford.edu
Center for the Study of Language and Information (CSLI), Stanford CA
-- 
Juergen "Gandalf" Wagner,		   gandalf@csli.stanford.edu
Center for the Study of Language and Information (CSLI), Stanford CA

henrik@blblbl.UUCP (Larry DeLuca) (04/10/88)

In article <12840@brl-adm.ARPA>, mchinni@ardec.arpa (Michael J. Chinni, SMCAR-CCS-E) writes:
> In a message from Michael Deutsch  <deutsch@jplgodo.uucp> 
> dated 6 Apr 88 03:47:17 GMT he writes:
>  > exists.  In case the file DOES NOT exist I want
>  > the program to function identically but the results
>  > should be flushed down the tube, i.e. nowhere, i.e.

> I have a couple of suggestions.  First, try writing to "/dev/null".
> Second, a temporary file could be used ("mkstemp" in BSD or "tmpnam"/"tempnam"
> and "tmpfile" in SysV).  Last, a filename of personal choice could be created,
> written to, and then deleted at the end of the program.

Why not just open the file at the beginning, and use the file pointer returned
to tell if you should write or not, i.e.

	if(fp != (FILE *) 0)
		fprintf(...);

I usually encode things of this sort in a macro and it makes my life much 
easier.  Also, if you don't have the file around in the first place, things
will go much more quickly than writing to a temp file you're only going to 
delete, or writing to /dev/null.

					larry...

rbj@icst-cmr.arpa (Root Boy Jim) (04/26/88)

   [access is ...]
   ... for setuid programs to determine whether the real user can access
   a file.  (It's the wrong way to do even that, because of the resulting
   window, but that's another can of worms.)

OK, now to address that window issue. As I understand it, the basic
objection to access is a window of vulnerability between deciding if
access to a file is okay, and actually doing something to that file,
(for the purposes of this discussion, let's say open) someone could
possibly substitute another file. I propose enclosing the whole mess
between two stat's, and testing whether the vital statistics match.
Then, you can assume it's the same file you asks about via access.

					   der Mouse

			   uucp: mouse@mcgill-vision.uucp
			   arpa: mouse@larry.mcrcim.mcgill.edu

	(Root Boy) Jim Cottrell	<rbj@icst-cmr.arpa>
	National Bureau of Standards
	Flamer's Hotline: (301) 975-5688
	The opinions expressed are solely my own
	and do not reflect NBS policy or agreement
	Now, let's SEND OUT for QUICHE!!

mouse@larry.mcrcim.mcgill.edu (der Mouse) (04/26/88)

>> [access is ...] the wrong way to [determine whether the real user
>> can access a file], because of the resulting window, but that's
>> another can of worms.

> OK, now to address that window issue.  As I understand it, the basic
> objection to access is a window of vulnerability between deciding if
> access to a file is okay, and actually doing something to that file.

Precisely.  At least, that's the one I meant.

> I propose enclosing the whole mess between two stat's, and testing
> whether the vital statistics match.  Then, you can assume it's the
> same file you asks about via access.

Well, you have to be very careful, and even then, I don't think it's
possible to get it right.  You can make the window much harder to hit,
but I don't think it's possible to get rid of it entirely.  After much
arguing with myself, the closest I've come up with is

fd = open(path) <--- this must succeed, of course
access(path)    <--- this must show "access permitted"
stat(path)      <--\ These two must produce
fstat(fd)       <--/  matching stat structures

but this is vulnerable to a quick switch immediately before and after
the access() call.  Sprinkling extra calls (stat(), access(), etc)
around doesn't help, because if the switches occur exactly before and
after the access(), it is impossible to detect them.

None of this helps any when you want to find out whether it is safe to
create or remove a file.  For any sequence of operations, switching
symlinks immediately before (and after) an unlink() or
open(...,O_CREAT,...) can beat it.  Therefore, if a setuid program is
to create or remove a file, it must be very sure that the real user
cannot pull a switch on any directory on the path used to reach the
file in question.  Chdir() first (or create all your files in / :-)

What's more, when creating, O_EXCL must be used.  I shan't describe the
problem in detail, because I'm sure far too few setuid programs
actually do bother to use O_EXCL, and I don't want to be too blatant
about giving directions on how to break into a system.

What we need, it seems, is a way to say "I wish to restrict myself to
the real user's access privileges" for a time, with the potential to
revert back to the previous state.  4.3's setreuid() appears to be
exactly this, though I seem to recall that if the real uid is 0, the
process has superuser access even if the effective uid is not 0, which
would mean that this technique is useless for setuid-root programs.

It might be worth putting a flag into open() (O_REALUID maybe) to have
it use the real UID when it does permission checks, just as a case of
"make the common case easy".  Open already takes flags, so it would be
totally compatible.

Security is such a mess.  Why don't we all run GNU and leave this sort
of security to the security freaks?  Oh yes, that's right, GNU doesn't
exist yet.  Bother.

					der Mouse

			uucp: mouse@mcgill-vision.uucp
			arpa: mouse@larry.mcrcim.mcgill.edu

rbj@icst-cmr.arpa (Root Boy Jim) (04/29/88)

   From: der Mouse <mouse@larry.mcrcim.mcgill.edu>

Der Mouse und der Root Boy discussing access: I said...

   > I propose enclosing the whole mess between two stat's, and testing
   > whether the vital statistics match.  Then, you can assume it's the
   > same file you asks about via access.

To which he replied...

   Well, you have to be very careful, and even then, I don't think it's
   possible to get it right.  You can make the window much harder to hit,
   but I don't think it's possible to get rid of it entirely.  After much
   arguing with myself, the closest I've come up with is

   fd = open(path) <--- this must succeed, of course
   access(path)    <--- this must show "access permitted"
   stat(path)      <--\ These two must produce
   fstat(fd)       <--/  matching stat structures

   but this is vulnerable to a quick switch immediately before and after
   the access() call.  Sprinkling extra calls (stat(), access(), etc)
   around doesn't help, because if the switches occur exactly before and
   after the access(), it is impossible to detect them.

My idea is more explicitly

	stat(path,&before)
	if (access(path,how) == 0) { /*OK*/
		fd = open(path,mode);
		stat(path,&after);
		for (all relevant fields in struct stat) {
			if (before.field != after.field) {
				printf("fulling a fast one, eh?\n");
				exit(-1);
			}
		}
	} /* everything OK */

Good things to check would be inode number and creation date. Neither
can easily be faked, except thru acces to the raw device. I am assuming
previous existence of the file access'ed. If it doesn't already exist,
the job is a bit harder. Note also that the file should not be
truncated on the open, in case a fast one *is* being pulled.

Any problems with this approach?

BTW, while we're talking windows, mktemp et al suffer as well.

					   der Mouse

			   uucp: mouse@mcgill-vision.uucp
			   arpa: mouse@larry.mcrcim.mcgill.edu

	(Root Boy) Jim Cottrell	<rbj@icst-cmr.arpa>
	National Bureau of Standards
	Flamer's Hotline: (301) 975-5688
	The opinions expressed are solely my own
	and do not reflect NBS policy or agreement
How many retured bricklayers from FLORIDA are out purchasing
 PENCIL SHARPENERS right NOW??

mouse@larry.mcrcim.mcgill.edu (der Mouse) (04/29/88)

>>> I propose enclosing the whole mess between two stat's, and testing
>>> whether the vital statistics match.

>> Well, you have to be very careful, and even then, I don't think it's
>> possible to get it right.  You can make the window much harder to
>> hit, but I don't think it's possible to get rid of it entirely.

> My idea is more explicitly

>	stat(path,&before)
>	if (access(path,how) == 0) { /*OK*/
>		fd = open(path,mode);
>		stat(path,&after);
> [compare after.xxx and before.xxx for appropriate xxx]
>	}

> [later in letter] Any problems with this approach?

Yes.  Suppose we pull a switch immediately before and after the open().
You can't catch it, and can easily wind up opening the wrong file.
Specifically:

user does		program does

% touch myfile.blah
% setuidpgm myfile.blah
			stat(path,&before)
			if (access(path,how) == 0) { /*OK*/
% mv myfile.blah myfile.save
% ln -s /etc/passwd myfile.blah
				fd = open(path,mode);
% rm -f myfile.blah
% mv myfile.save myfile.blah
				stat(path,&after);
		[compare after.xxx and before.xxx for appropriate xxx]
			}

(Of course, those wouldn't actually be typed commands; I'm just using
the commands to indicate the operations being performed.)  This is why
I recommend doing fstat() on the file descriptor returned by open(), to
ensure that the file you really opened is the one you were stat()ing.
But that's vulnerable too; think about something similar to the above
with the secure file in place at the beginning, replaced with an
innocuous file for the access() to see.

(You can see why I said the window is "much" harder to hit.  The bad
guy must time two operations exactly right instead of just one, and the
interval between the two is very short.)

> Good things to check would be inode number and creation date.

Do any recent systems maintain a creation date?  4.[23]BSD doesn't.
(st_ctime is the inode-change time, not the creation time.)

> Neither can easily be faked, except thru acces to the raw device.

If I wanted to patch a ctime field, I'd much rather change the clock
and touch the file than meddle with the raw disk.

> If [the file being open()ed] doesn't already exist, the job is a bit
> harder.

I claim it's impossible if the user has write access to any of the
ancestors of the directory the file is to be created in.

> BTW, while we're talking windows, mktemp et al suffer as well.

Hence mkstemp(3).

					der Mouse

			uucp: mouse@mcgill-vision.uucp
			arpa: mouse@larry.mcrcim.mcgill.edu

rbj@icst-cmr.arpa (Root Boy Jim) (04/29/88)

OK, so expand the definion of `stat' to include `fstat' *and* `lstat'.

Expanding the window to two windows *should* provide enuf security, altho
I agree with your idea of a specific flag to open to say `use the real uid
rather than the effective uid (maybe Chris can hack this in tonite :-).

Anyone having permission to change the date or use the raw device is
*already* root, and so won't have to resort to this trickery.

And of course, Chris will want a seventh arg on select or a fourth on open :-)

	(Root Boy) Jim Cottrell	<rbj@icst-cmr.arpa>
	National Bureau of Standards
	Flamer's Hotline: (301) 975-5688
	The opinions expressed are solely my own
	and do not reflect NBS policy or agreement
	It's OKAY --- I'm an INTELLECTUAL, too.