Unix Hackers in our OpenPKG community, please review the following
security-sensitive patch to the OpenPKG bootstrap package.

It wraps the <prefix>/bin/openpkg command with a small SetUID program
which executes the command under a particular user according to the
following table:

----------------------------- --------------------- ---------------
Caller is matched by entry in Command requires      Command is then
<prefix>/etc/openpkg/managers super-user privileges executed under
----------------------------- --------------------- ---------------
no                            no                    caller
no                            yes                   caller
yes                           no                    management user
yes                           yes                   super user
----------------------------- --------------------- ---------------

By default the <prefix>/etc/openpkg/managers contains only entries
corresponding to the OpenPKG instance management user (%{l_musr}, e.g.
"openpkg") and the super user (%{l_susr}, e.g. "root").

As a result this especially means:

1. For regular users and their usage of <prefix>/bin/openpkg
   nothing changes. They are not affected by the feature at all.

2. The management user is able to now really manage the complete
   instance as it now is able to build and install packages, stop and
   start services, etc. For instance if it runs "<prefix>/etc/openpkg
   --rebuild ..." this is still done under his privileges, but the
   following "<prefix>/etc/openpkg -Uvh ..." is done with root
   privileges. This means no more "sudo" setups required for the
   management user and especially the OpenPKG handling is much
   simplified for the average user as he no longer has to think about
   whether he has to switch to or from root for the next command all the
   time.

3. The super user is now downgraded to the management user for
   commands which do not require super user privileges. This especially
   means that if the super user executes a "<prefix>/etc/openpkg
   --rebuild ..." it automatically internally is executed under the
   management user. This way no more packages _CAN_ be built under
   root privileges. This is a further improvement in security and also
   improves the OpenPKG "sane build environment" idea.

The only less precise part of this feature is the determination whether
a command really requires super user privileges. Without really
parsing the complete "openpkg rpm" and "openpkg rc" command line we
can only guess on this. Please review this part very carefully. We
detected no problems until now, but there might be command lines
where we might guess incorrectly. For this situation there is a
"<prefix>/bin/openpkg --keep-privileges" option which disables the whole
privilege upgrading/downgrading at all and this way allows one to easily
circumvent a situation where we incorrectly guess the command line.

For your security review of this patch please keep especially in mind
that the OpenPKG management user from a security point of view always
_was_ and still _is_ fully equal to the super user. In case you forgot
this, the reason simply is that if you have the privileges of the
management user you always can become the super user by just editing an
arbitrary rc file of a daemon or something similar. So, we do NOT change
the security paranoid aspects just by allowing the management user to
automatically upgrade to the super user now.

One last word: the stuff is controlled by a
<prefix>/etc/openpkg/managers file. Usually this shouldn't be changed.
But with great care one _can_ add a regular user to this configuration
file and this way allow him to also manage the OpenPKG instance. One
just has to keep in mind that this way one makes the regular user
security wise equal to the super and management users.

As always, your feedback is highly appreciated!

                                       Ralf S. Engelschall
                                       [EMAIL PROTECTED]
                                       www.engelschall.com

Index: openpkg.c
===================================================================
RCS file: openpkg.c
diff -N openpkg.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ openpkg.c   19 Aug 2006 08:23:41 -0000
@@ -0,0 +1,306 @@
+/*
+**  openpkg -- OpenPKG Tool Chain
+**  Copyright (c) 2000-2006 OpenPKG Foundation e.V. <http://openpkg.net/>
+**  Copyright (c) 2000-2006 Ralf S. Engelschall <http://engelschall.com/>
+**
+**  Permission to use, copy, modify, and distribute this software for
+**  any purpose with or without fee is hereby granted, provided that
+**  the above copyright notice and this permission notice appear in all
+**  copies.
+**
+**  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+**  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+**  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+**  IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
+**  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+**  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+**  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+**  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+**  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+**  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+**  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+**  SUCH DAMAGE.
+**
+**  openpkg.c: Execution Wrapper (Language: C)
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <pwd.h>
+#include <grp.h>
+#include <unistd.h>
+#include <errno.h>
+
+/* sanity check compilation */
+#ifndef OPENPKG_PREFIX
+#error OpenPKG instance prefix not defined
+#endif
+#ifndef OPENPKG_SUSR
+#error OpenPKG super user not defined
+#endif
+#ifndef OPENPKG_MUSR
+#error OpenPKG management user not defined
+#endif
+
+/* platform specifics */
+#if defined(OPENPKG_PLATFORM_FREEBSD) || \
+    defined(OPENPKG_PLATFORM_NETBSD)  || \
+    defined(OPENPKG_PLATFORM_OPENBSD) || \
+    defined(OPENPKG_PLATFORM_SUNOS)   || \
+    defined(OPENPKG_PLATFORM_LINUX)
+#define HAVE_INITGROUPS
+#endif
+
+/* helper function for printing a warning message */
+static void warn(const char *fmt, ...)
+{
+    va_list ap;
+
+    va_start(ap, fmt);
+    fprintf(stderr, "openpkg:WARNING: ");
+    vfprintf(stderr, fmt, ap);
+    fprintf(stderr, "\n");
+    va_end(ap);
+    return;
+}
+
+/* helper function for printing a fatal message and exit */
+static void fatal(const char *fmt, ...)
+{
+    va_list ap;
+
+    va_start(ap, fmt);
+    fprintf(stderr, "openpkg:ERROR: ");
+    vfprintf(stderr, fmt, ap);
+    fprintf(stderr, "\n");
+    va_end(ap);
+    exit(1);
+    return;
+}
+
+/* adjust process privileges */
+static void adjust_privileges(int uid, int gid, int login)
+{
+    struct passwd *pw;
+
+    /* optionally emulate a more complete login */
+    if (login) {
+        /* determine information about user id */
+        if ((pw = getpwuid(uid)) == NULL)
+
+        fatal("unable to resolve user id \"%d\": %s\n", uid, strerror(errno));
+        /* reset some essential environment variables */
+        setenv("LOGNAME", pw->pw_name,   1);
+        setenv("USER",    pw->pw_name,   1);
+        setenv("SHELL",   pw->pw_shell,  1);
+        setenv("HOME",    pw->pw_dir,    1);
+
+#ifdef HAVE_INITGROUPS
+        /* initialize complete group access list */
+        if (initgroups(pw->pw_name, pw->pw_gid) == -1)
+            fatal("failed to initialize access group list via initgroups(3): 
%s", strerror(errno));
+#endif
+    }
+
+    /* switch to group id (first) */
+    if (setgid(gid) == -1)
+        fatal("failed to set group id via setgid(2): %s", strerror(errno));
+
+    /* switch to user id (second) */
+    if (setuid(uid) == -1)
+        fatal("failed to set user id via setuid(2): %s", strerror(errno));
+
+    return;
+}
+
+/* main program */
+int main(int argc, char **argv, char **envp)
+{
+    int keep_original_privileges;
+    int is_manager;
+    int require_superuser;
+    char buf[1024];
+    char *username;
+    char *groupname;
+    char *filename;
+    char *cp;
+    int my_uid, my_euid;
+    int my_gid, my_egid;
+    int m_uid, m_gid;
+    int s_uid, s_gid;
+    FILE *fp;
+    struct passwd *pw;
+    struct group *gr;
+    int i, j;
+    int ok_uid;
+    int ok_gid;
+
+    /* determine our current real and effective user/group ids */
+    my_uid  = getuid();
+    my_gid  = getgid();
+    my_euid = geteuid();
+    my_egid = getegid();
+
+    /* determine superuser/management user/group id */
+    if ((pw = getpwnam(OPENPKG_SUSR)) == NULL)
+        fatal("unable to resolve OpenPKG superuser username \"%s\": %s\n", 
OPENPKG_SUSR, strerror(errno));
+    s_uid = (int)pw->pw_uid;
+    s_gid = (int)pw->pw_gid;
+    if ((pw = getpwnam(OPENPKG_MUSR)) == NULL)
+        fatal("unable to resolve OpenPKG management username \"%s\": %s\n", 
OPENPKG_MUSR, strerror(errno));
+    m_uid = (int)pw->pw_uid;
+    m_gid = (int)pw->pw_gid;
+
+    /* check whether we are forced to keep original privileges
+       (mainly in order to circumvent problems in case our
+       "require_superuser" guessing below is too weak!) */
+    keep_original_privileges = 0;
+    if (argc > 1 && strcmp(argv[1], "--keep-privileges") == 0) {
+        keep_original_privileges = 1;
+        argv++;
+        argc--;
+    }
+
+    /* read list of explicitly configured management users */
+    is_manager = 0;
+    if (!keep_original_privileges) {
+        filename = OPENPKG_PREFIX "/etc/openpkg/managers";
+        if ((fp = fopen(filename, "r")) == NULL)
+            warn("unable to open configuration file \"%s\": %s", 
OPENPKG_PREFIX "/etc/openpkg/managers", strerror(errno));
+        else {
+            while ((cp = fgets(buf, sizeof(buf), fp)) != NULL) {
+                /* parse entry as "<username>[:<groupname>]" */
+                username = buf + strspn(buf, " \t");
+                cp = username + strcspn(username, " \t\r\n");
+                *cp = '\0';
+                if (username[0] == '#' || username[0] == '\r' || username[0] 
== '\n' || username[0] == '\0')
+                    continue;
+                groupname = "*";
+                if ((cp = strchr(username, ':')) != NULL) {
+                    *cp++ = '\0';
+                    groupname = cp;
+                }
+
+                /* check whether UID is ok */
+                ok_uid = 0;
+                if (strcmp(username, "*") == 0)
+                    ok_uid = 1;
+                else {
+                    if ((pw = getpwnam(username)) == NULL) {
+                        warn("invalid username \"%s\" in \"%s\"\n", username, 
filename);
+                        continue;
+                    }
+                    if ((int)pw->pw_uid == my_uid)
+                        ok_uid = 1;
+                }
+
+                /* check whether GID is ok */
+                ok_gid = 0;
+                if (strcmp(groupname, "*") == 0)
+                    ok_gid = 1;
+                else {
+                    if ((gr = getgrnam(groupname)) == NULL) {
+                        warn("invalid groupname \"%s\" in \"%s\"\n", 
groupname, filename);
+                        continue;
+                    }
+                    if ((int)gr->gr_gid == my_gid)
+                        ok_gid = 1;
+                }
+
+                /* if both UID and GID are ok, user is manager */
+                if (ok_uid && ok_gid) {
+                    is_manager = 1;
+                    break;
+                }
+            }
+            fclose(fp);
+        }
+    }
+
+    /* determine whether command requires super-user privileges */
+    require_superuser = 0;
+    if (argc > 1 && strcmp(argv[1], "rpm") == 0) {
+        for (i = 2; i < argc; i++) {
+            if (strcmp(argv[i], "--") == 0)
+                break;
+            else if (   strcmp(argv[i], "--erase")      == 0
+                     || strcmp(argv[i], "--freshen")    == 0
+                     || strcmp(argv[i], "--install")    == 0
+                     || strcmp(argv[i], "--upgrade")    == 0
+                     || strcmp(argv[i], "--import")     == 0
+                     || strcmp(argv[i], "--initdb")     == 0
+                     || strcmp(argv[i], "--rebuilddb")  == 0
+                     || strcmp(argv[i], "--db-build")   == 0
+                     || strcmp(argv[i], "--db-rebuild") == 0
+                     || strcmp(argv[i], "--db-cleanup") == 0
+                     || strcmp(argv[i], "--db-fixate")  == 0
+                     || strcmp(argv[i], "--setperms")   == 0
+                     || strcmp(argv[i], "--setugids")   == 0) {
+                require_superuser = 1;
+                break;
+            }
+            else if (argv[i][0] == '-' && argv[i][1] != '-') {
+                for (j = 1; argv[i][j] != '\0'; j++) {
+                    if (    (   argv[i][j] == 'q'
+                             || argv[i][j] == 'V'
+                             || argv[i][j] == 'K')
+                         && argv[i][j+1] != '\0') {
+                        j++;
+                        continue;
+                    }
+                    else if (   argv[i][j] == 'i'
+                        || argv[i][j] == 'U'
+                        || argv[i][j] == 'F'
+                        || argv[i][j] == 'e') {
+                        require_superuser = 1;
+                        break;
+                    }
+                }
+                if (require_superuser)
+                    break;
+            }
+        }
+    }
+    else if (argc > 1 && strcmp(argv[1], "rc") == 0) {
+        require_superuser = 1;
+        for (i = 2; i < argc; i++) {
+            if (strcmp(argv[i], "--") == 0)
+                break;
+            else if (   strcmp(argv[i], "-q")       == 0
+                     || strcmp(argv[i], "--query")  == 0
+                     || strcmp(argv[i], "-c")       == 0
+                     || strcmp(argv[i], "--config") == 0) {
+                require_superuser = 0;
+                break;
+            }
+        }
+    }
+
+    /* adjust privileges according to determined information */
+    if (!keep_original_privileges && require_superuser && is_manager && 
my_euid == 0) {
+        /* increase privileges to super user */
+        adjust_privileges(s_uid, s_gid, 1);
+    }
+    else if (!keep_original_privileges && !require_superuser && is_manager && 
my_euid == 0) {
+        /* decrease privileges to management user */
+        adjust_privileges(m_uid, m_gid, 1);
+    }
+    else /* keep_original_privileges || !is_manager */ {
+        /* drop privileges */
+        adjust_privileges(my_uid, my_gid, 0);
+    }
+
+    /* pass-through control to real Execution Frontend (shell script) */
+    argv[0] = OPENPKG_PREFIX "/lib/openpkg/openpkg";
+    if (execve(argv[0], argv, envp) == -1)
+        fatal("failed to execute \"%s\": %s", argv[0], strerror(errno));
+
+    /* NOT REACHED */
+    fatal("INTERNAL ERROR");
+    return 0;
+}
+
Index: openpkg.boot
===================================================================
RCS file: /v/openpkg/cvs/openpkg-src/openpkg/openpkg.boot,v
retrieving revision 1.59
diff -u -d -u -d -u -d -r1.59 openpkg.boot
--- openpkg.boot        22 Jun 2006 08:01:37 -0000      1.59
+++ openpkg.boot        19 Aug 2006 08:23:41 -0000
@@ -490,7 +490,7 @@
 files=`cat $spec |\
        sed -e '1,/%files/d' -e '/%clean/,$d' |\
        grep -v '^ *$' | grep -v '%defattr' |\
-       sed -e 's;%config(noreplace) *;;' -e 's;%config *;;' -e 's;%ghost *;;' \
+       sed -e 's;%config(noreplace) *;;' -e 's;%config *;;' -e 's;%ghost *;;' 
-e 's;%attr([^)]*) *;;' \
            -e 's;%dir *;;' -e 's;%{l_prefix}/;;' -e 's;^ *;;' -e 
"s;%{V_rpm};${V_rpm};"`
 db_files=""
 for db_file in \
Index: openpkg.spec
===================================================================
RCS file: /v/openpkg/cvs/openpkg-src/openpkg/openpkg.spec,v
retrieving revision 1.511
diff -u -d -u -d -u -d -r1.511 openpkg.spec
--- openpkg.spec        18 Aug 2006 13:17:10 -0000      1.511
+++ openpkg.spec        19 Aug 2006 08:23:41 -0000
@@ -144,6 +144,7 @@
 Source69:     release.sh
 Source70:     release.pod
 Source71:     release.8
+Source72:     openpkg.c

 #   build information
 Prefix:       %{l_prefix}
@@ -942,6 +943,19 @@
       ${l_make}
     ) || exit $?

+    #   build frontend wrapper
+    ( os_name=`(uname -s) 2>/dev/null` || os_name='Unknown'
+      os_name=`echo "${os_name}" |\
+               sed -e 's;[^a-zA-Z0-9];;g' |\
+               tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'`
+      ${l_cc} \
+          "-DOPENPKG_PLATFORM_${os_name}" \
+          '-DOPENPKG_PREFIX="%{l_prefix}"' \
+          '-DOPENPKG_SUSR="%{l_susr}"' \
+          '-DOPENPKG_MUSR="%{l_musr}"' \
+          -o openpkg `SOURCE openpkg.c`
+    ) || exit $?
+
 %install
     #   skip in bootstrap phase 2 (see openpkg.boot)
     [ ".$OPENPKG_BOOT" = .1 ] && exit 0
@@ -1198,10 +1212,13 @@
         <`SOURCE rpmtool` >$RPM_BUILD_ROOT%{l_prefix}/lib/openpkg/rpmtool
     chmod a+x $RPM_BUILD_ROOT%{l_prefix}/lib/openpkg/rpmtool

-    #   install OpenPKG tool chain execution frontend
+    #   install OpenPKG tool chain execution frontend and execution wrapper
+    cp openpkg $RPM_BUILD_ROOT%{l_prefix}/bin/openpkg
+    ${l_strip} $RPM_BUILD_ROOT%{l_prefix}/bin/openpkg
+    chmod 4775 $RPM_BUILD_ROOT%{l_prefix}/bin/openpkg
     sed -e "s;@l_prefix@;%{l_prefix};g" \
-        <`SOURCE openpkg.sh` >$RPM_BUILD_ROOT%{l_prefix}/bin/openpkg
-    chmod 755 $RPM_BUILD_ROOT%{l_prefix}/bin/openpkg
+        <`SOURCE openpkg.sh` >$RPM_BUILD_ROOT%{l_prefix}/lib/openpkg/openpkg
+    chmod 755 $RPM_BUILD_ROOT%{l_prefix}/lib/openpkg/openpkg
     sed -e "s:@l_prefix@:%{l_prefix}:g" \
         <`SOURCE openpkg.1` \
         >$RPM_BUILD_ROOT%{l_prefix}/man/man1/openpkg.1
@@ -1290,6 +1307,16 @@
         $RPM_BUILD_ROOT%{l_prefix}/etc/openpkg/register.tran \
         $RPM_BUILD_ROOT%{l_prefix}/etc/openpkg/register.util

+    #   install default managers configuration file
+    ( echo "##"
+      echo "##  managers -- OpenPKG Instance Managers"
+      echo "##"
+      echo ""
+      echo "%{l_susr}"
+      echo "%{l_musr}"
+      echo ""
+    ) >$RPM_BUILD_ROOT%{l_prefix}/etc/openpkg/managers
+
     #   install overriding RPM configuration files
     sed -e "s:@l_prefix@:%{l_prefix}:g" \
         <`SOURCE rpmpopt` \
@@ -1331,7 +1358,7 @@
     %dir %{l_prefix}/RPM/TMP
     %dir %{l_prefix}/cgi
     %dir %{l_prefix}/bin
-    %{l_prefix}/bin/openpkg
+    %attr(4755,%{l_susr},%{l_mgrp}) %{l_prefix}/bin/openpkg
     %dir %{l_prefix}/etc
     %{l_prefix}/etc/rc
     %config(noreplace) %{l_prefix}/etc/rc.conf
@@ -1346,6 +1373,7 @@
     %ghost %{l_prefix}/etc/openpkg/register.prep
     %ghost %{l_prefix}/etc/openpkg/register.tran
     %ghost %{l_prefix}/etc/openpkg/register.util
+    %config %{l_prefix}/etc/openpkg/managers
     %{l_prefix}/etc/openpkg/openpkg.pgp
     %dir %{l_prefix}/include
     %dir %{l_prefix}/include/openpkg
@@ -1429,6 +1457,7 @@
     %{l_prefix}/lib/openpkg/librpmio.a
     %{l_prefix}/lib/openpkg/librpmpopt.a
     %{l_prefix}/lib/openpkg/librpmz.a
+    %{l_prefix}/lib/openpkg/openpkg
     %dir %{l_prefix}/libexec
     %dir %{l_prefix}/libexec/openpkg
     %{l_prefix}/libexec/openpkg/uuid

______________________________________________________________________
The OpenPKG Project                                    www.openpkg.org
Developer Communication List                   openpkg-dev@openpkg.org

Reply via email to