Subject: pkg/6009: pkg_add fails if PREFIX is a symbolic link
To: None <gnats-bugs@gnats.netbsd.org>
From: None <jbernard@ox.mines.edu>
List: netbsd-bugs
Date: 08/23/1998 19:33:03
>Number:         6009
>Category:       pkg
>Synopsis:       pkg_add fails if PREFIX is a symbolic link
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    gnats-admin (GNATS administrator)
>State:          open
>Class:          change-request
>Submitter-Id:   net
>Arrival-Date:   Sun Aug 23 18:50:00 1998
>Last-Modified:
>Originator:     Jim Bernard
>Organization:
	Speaking for myself
>Release:        August 22, 1998
>Environment:
System: NetBSD zoo 1.3F NetBSD 1.3F (ZOO) #0: Sun Jun 14 09:09:08 MDT 1998 local@zoo:/home/local/compile/sys/arch/i386/compile/ZOO i386


>Description:
	If, say, /usr/X11R6 is a symbolic link, then pkg_add will fail
	to install a binary package because it stats the link, not the
	directory to which it points.  Now I _swear_ this used to work,
	but it's quite clearly broken now.

>How-To-Repeat:
	Make a binary package.  Try to install it with pkg_add, which will
	fail.

>Fix:
	The problem is that in make_hierarchy (in
	usr.sbin/pkg_install/add/futil.c) each component of the path is
	tested with the function isdir (in usr.sbin/pkg_install/lib/file.c),
	which uses lstat and tests for a directory with S_ISDIR.  This test
	fails on a symbolic link, since the returned stat structure refers
	to the link, not the directory it points to.

	Since (1) there may be other sections of the code that really want
	to use isdir to check exclusively for real directories, and (2)
	there's a (small) security risk involved in simply following
	symbolic links during an important operation like installing
	packages as root, I decided to add a second function, "islinktodir"
	to test for links to directories.  Then the test in make_hierarchy
	becomes isdir || islinktodir.

	The islinktodir function checks first that the file is a link,
	and if so, checks that the directory in which the link resides
	is owned by root or the invoking user and does not have group
	or other write permission.  If that test succeeds, it checks
	to see that the object pointed to by the link exists and is
	a directory.

	Patches are to usr.sbin/pkg_install/{lib/file.c,lib/lib.h,add/futil.c}.

--- lib/file.c-dist	Fri Jul 10 05:17:10 1998
+++ lib/file.c	Sun Aug 23 15:07:50 1998
@@ -61,10 +61,42 @@
 	return TRUE;
     else
 	return FALSE;
 }
 
+/*
+ * Check to see if something is a (safe) symbolic link to a directory.
+ * Here "safe" means that the directory containing the link is owned
+ * by root or the invoking user and does not have group or other write
+ * permission.
+ */
+Boolean
+islinktodir(char *fname)
+{
+    struct stat sb;
+    char c, *cp;
+    uid_t me;
+
+    if (lstat(fname, &sb) != FAIL && S_ISLNK(sb.st_mode)) {
+	if ((cp = strrchr(fname, '/')) == NULL || cp == fname)
+	    return FALSE;		/* No bare names or "/" as link */
+	c = *cp;
+	*cp = '\0';
+	me = geteuid();
+	if (stat(fname, &sb) != FAIL && S_ISDIR(sb.st_mode)
+	    && (sb.st_uid == 0 || sb.st_uid == me)
+	    && (sb.st_mode & (S_IWGRP|S_IWOTH)) == 0)
+	{
+	    *cp = c;
+	    if (stat(fname, &sb) != FAIL && S_ISDIR(sb.st_mode))
+		return TRUE;
+	}
+	*cp = c;
+    }
+    return FALSE;
+}
+
 /* Check to see if file is a dir, and is empty */
 Boolean
 isemptydir(char *fname)
 {
     if (isdir(fname)) {
--- lib/lib.h-dist	Mon Jul  6 05:13:39 1998
+++ lib/lib.h	Sun Aug 23 15:14:55 1998
@@ -124,10 +124,11 @@
 char		*strconcat(char *, char *);
 
 /* File */
 Boolean		fexists(char *);
 Boolean		isdir(char *);
+Boolean		islinktodir(char *);
 Boolean		isemptydir(char *fname);
 Boolean		isemptyfile(char *fname);
 Boolean         isfile(char *);
 Boolean		isempty(char *);
 Boolean		isURL(char *);
--- add/futil.c-dist	Sat Oct 18 13:40:05 1997
+++ add/futil.c	Sun Aug 23 15:10:52 1998
@@ -49,11 +49,11 @@
 	cp1 = cp2 = dir;
     while (cp2) {
 	if ((cp2 = strchr(cp1, '/')) !=NULL )
 	    *cp2 = '\0';
 	if (fexists(dir)) {
-	    if (!isdir(dir))
+	    if (!(isdir(dir) || islinktodir(dir)))
 		return FAIL;
 	}
 	else {
 	    if (vsystem("mkdir %s", dir))
 		return FAIL;
>Audit-Trail:
>Unformatted: