From jhein@timing.com  Wed May  2 00:46:13 2007
Return-Path: <jhein@timing.com>
Received: from mx1.freebsd.org (mx1.freebsd.org [69.147.83.52])
	by hub.freebsd.org (Postfix) with ESMTP id 110A616A402
	for <FreeBSD-gnats-submit@freebsd.org>; Wed,  2 May 2007 00:46:13 +0000 (UTC)
	(envelope-from jhein@timing.com)
Received: from Daffy.timing.com (daffy.timing.com [206.168.13.218])
	by mx1.freebsd.org (Postfix) with ESMTP id CA18813C489
	for <FreeBSD-gnats-submit@freebsd.org>; Wed,  2 May 2007 00:46:12 +0000 (UTC)
	(envelope-from jhein@timing.com)
Received: from gromit.timing.com (gromit.timing.com [206.168.13.209])
	by Daffy.timing.com (8.13.1/8.13.1) with ESMTP id l420BDND048164;
	Tue, 1 May 2007 18:11:13 -0600 (MDT)
	(envelope-from jhein@timing.com)
Received: from gromit.timing.com (localhost [127.0.0.1])
	by gromit.timing.com (8.13.8/8.13.8) with ESMTP id l420BCUb051836;
	Tue, 1 May 2007 18:11:13 -0600 (MDT)
	(envelope-from jhein@gromit.timing.com)
Received: (from jhein@localhost)
	by gromit.timing.com (8.13.8/8.13.8/Submit) id l420BC2t051835;
	Tue, 1 May 2007 18:11:12 -0600 (MDT)
	(envelope-from jhein)
Message-Id: <200705020011.l420BC2t051835@gromit.timing.com>
Date: Tue, 1 May 2007 18:11:12 -0600 (MDT)
From: "John E. Hein" <jhein@timing.com>
To: FreeBSD-gnats-submit@freebsd.org
Cc: jhein@timing.com
Subject: install -S (safe copy) with -C or -p is not so safe
X-Send-Pr-Version: 3.113
X-GNATS-Notify:

>Number:         112336
>Category:       bin
>Synopsis:       [patch] install(1): install -S (safe copy) with -C or -p is not so safe
>Confidential:   no
>Severity:       serious
>Priority:       low
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:  
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Wed May 02 00:50:03 GMT 2007
>Closed-Date:    
>Last-Modified:  Sat Jun 14 06:02:08 UTC 2008
>Originator:     John E. Hein
>Release:        FreeBSD 6.2-STABLE i386
>Organization:
Symmetricom, Inc.
>Environment:
System: FreeBSD gromit.timing.com 6.2-STABLE FreeBSD 6.2-STABLE #2: Wed Mar 21 09:48:47 MDT 2007 jhein@gromit.timing.com:/usr/obj/usr/src/sys/GROMIT i386


>Description:

When using install -S, in certain cases, install will unlink
the destination file defeating the point of using -S.

fred & joe are both in the operator group

% su - fred
% mkdir /tmp/dest
% chgrp operator /tmp/dest
% chmod 775 /tmp/dest

% touch foo
% install -S -C -m 0755 foo /tmp/dest
% ls -l /tmp/dest/foo
-rwxr-xr-x   1 joe  operator     0 Apr 27 18:02 /tmp/dest/foo*

% su - joe
% touch foo
% install -S -C -m 0644 foo /tmp/dest
install: /tmp/dest/foo: chmod: Operation not permitted

% ls /tmp/dest/foo
ls: /tmp/dest/foo: No such file or directory


There are other ways to recreate this.  For instance, if the files compare
the same [1], and the user can write in the directory, but the file has the
wrong ownership (as opposed to the wrong perms shown above), for
instance.

[1] per the content comparison done by compare() in
    usr.bin/xinstall/xinstall.c

This happens when -C or -p is used and the file content is the same.
Then the creation of the temp file used by -S is skipped and the
chown/chmod is attempted directly on the destination file.


Side issue:
-----------
I'm not really sure what the rationale is for unlink(2)-ing the file
if the chmod/chown fails.  It was surprising to me to have the file
deleted if the chmod fails (even without -S), but that's been in
xinstall.c since 1.1.  I'd be inclined to remove the unlink(2) in
these cases, but that might be break POLA for some, and I may have not
considered cases where it might be useful or expected (are there any?).  In
any case, that's a separate bug.



>How-To-Repeat:

See above.

>Fix:

There are at least a couple ways to fix the disappearing file issue
when using safe copy mode:


1) Always operate on the temp file with -S.  If you have any errors unlink
  the temp file rather than the target file.

  pros: You create the file from scratch, so you will have no
        problems with chmod on a file you own.

  cons: You lose speedup if file content is the same.

        You can't chmod the dest file in place, so if you
        don't have write perms on the dir, it will fail
        (but that should be expected since it is documented
         that -S needs to write in the directory, so this
         is a minor con, IMO - but install(1) should probably
         talk about directory perms for -S)

2) If the files compare identically, check the perms/ownership, and if
  chown/chmod needs to be run, fall back to creating the temp file
  (maybe do the perms/ownership check first since that will be faster
  than content comparison).

  pros: You don't waste time creating a temp file if not needed.

  cons: Code complexity.


I'm trying to decide which of 1 or 2 to do in a patch.
If there is a better way, let me know.
>Release-Note:
>Audit-Trail:

From: John E Hein <jhein@timing.com>
To: FreeBSD-gnats-submit@FreeBSD.org, freebsd-bugs@FreeBSD.org
Cc:  
Subject: Re: bin/112336: install -S (safe copy) with -C or -p is not so safe
Date: Mon, 7 May 2007 15:13:24 -0600

 Here is a patch that implements 2 and was simpler than I thought it
 would be.
 
 It creates the temp file and does a rename, not only if the contents
 of the "from" & "to" files don't match, but also if perms/ownership
 don't match.
 
 One optimization could be to try to do the chown/chmod first and if
 that succceeds and the files match contents, don't bother with the
 temp file.  But that seems to be needlessly complex for the relatively
 miniscule number of cases where we could gain from the added pedantry.
 And there may be edge case failures associated with an early
 chmod/chown.
 
 Any committers willing to take this one?
 
 
 Index: src/usr.bin/xinstall/xinstall.c
 ===================================================================
 RCS file: /base/FreeBSD-CVS/src/usr.bin/xinstall/xinstall.c,v
 retrieving revision 1.67
 diff -u -p -r1.67 xinstall.c
 --- src/usr.bin/xinstall/xinstall.c	6 Mar 2006 21:52:59 -0000	1.67
 +++ src/usr.bin/xinstall/xinstall.c	7 May 2007 20:52:18 -0000
 @@ -317,7 +317,19 @@ install(const char *from_name, const cha
  	if (docompare && !dostrip && target) {
  		if ((to_fd = open(to_name, O_RDONLY, 0)) < 0)
  			err(EX_OSERR, "%s", to_name);
 -		if (devnull)
 +		/*
 +		 * Even if the contents are the same, we want to rename
 +		 * temp files when doing a "safe" copy if the
 +		 * permissions and ownership need to change.  We may
 +		 * not have permission to chown/chmod the "to" file
 +		 * directly.
 +		 */
 +		if (tempcopy &&
 +		    ((gid != (gid_t)-1 && gid != to_sb.st_gid) ||
 +		    (uid != (uid_t)-1 && uid != to_sb.st_uid) ||
 +		    (mode != (to_sb.st_mode & ALLPERMS))))
 +		    files_match = 0;
 +		else if (devnull)
  			files_match = to_sb.st_size == 0;
  		else
  			files_match = !(compare(from_fd, from_name,

From: John E Hein <jhein@timing.com>
To: FreeBSD-gnats-submit@FreeBSD.org, freebsd-bugs@FreeBSD.org
Cc:  
Subject: Re: bin/112336: install -S (safe copy) with -C or -p is not so safe
Date: Sat, 15 Dec 2007 13:34:12 -0700

 Here is an update to the patch to refresh it after a recent commit to
 xinstall.c and to additionally check euid which is important in some
 non-superuser cases.
 
 Index: xinstall.c
 ===================================================================
 RCS file: /base/FreeBSD-CVS/src/usr.bin/xinstall/xinstall.c,v
 retrieving revision 1.68
 diff -u -p -r1.68 xinstall.c
 --- xinstall.c	14 Dec 2007 08:46:57 -0000	1.68
 +++ xinstall.c	15 Dec 2007 20:21:35 -0000
 @@ -278,6 +278,7 @@ install(const char *from_name, const cha
  	int devnull, files_match, from_fd, serrno, target;
  	int tempcopy, temp_fd, to_fd;
  	char backup[MAXPATHLEN], *p, pathbuf[MAXPATHLEN], tempfile[MAXPATHLEN];
 +	uid_t euid;
  
  	files_match = 0;
  	from_fd = -1;
 @@ -322,7 +323,20 @@ install(const char *from_name, const cha
  	if (docompare && !dostrip && target) {
  		if ((to_fd = open(to_name, O_RDONLY, 0)) < 0)
  			err(EX_OSERR, "%s", to_name);
 -		if (devnull)
 +		/*
 +		 * Even if the contents are the same, we want to rename
 +		 * temp files when doing a "safe" copy if the
 +		 * permissions and ownership need to change.  We may
 +		 * not have permission to chown/chmod the "to" file
 +		 * directly.
 +		 */
 +		if (tempcopy && (euid = geteuid()) != 0 &&
 +		    euid != to_sb.st_uid &&
 +		    ((gid != (gid_t)-1 && gid != to_sb.st_gid) ||
 +		    (uid != (uid_t)-1 && uid != to_sb.st_uid) ||
 +		    (mode != (to_sb.st_mode & ALLPERMS))))
 +		    files_match = 0;
 +		else if (devnull)
  			files_match = to_sb.st_size == 0;
  		else
  			files_match = !(compare(from_fd, from_name,
>Unformatted:
