Browse Source

setgroups() is not as easy afterall, steals some hacks from glibc to handle group limits

Ask Solem 13 years ago
parent
commit
ff9ce794e3
1 changed files with 55 additions and 12 deletions
  1. 55 12
      celery/platforms.py

+ 55 - 12
celery/platforms.py

@@ -283,7 +283,9 @@ def detached(logfile=None, pidfile=None, uid=None, gid=None, umask=0,
     workdir = os.getcwd() if workdir is None else workdir
 
     signals.reset("SIGCLD")  # Make sure SIGCLD is using the default handler.
-    set_effective_user(uid=uid, gid=gid)
+    if not os.geteuid():
+        # no point trying to setuid unless we're root.
+        maybe_drop_privileges(uid=uid, gid=gid)
 
     # Since without stderr any errors will be silently suppressed,
     # we need to know that we have access to the logfile.
@@ -330,12 +332,49 @@ def parse_gid(gid):
         raise
 
 
-def setgroups(uid):
+def _setgroups_hack(groups):
+    """:fun:`setgroups` may have a platform-dependent limit,
+    and it is not always possible to know in advance what this limit
+    is, so we use this ugly hack stolen from glibc."""
+    groups = groups[:]
+
+    while 1:
+        try:
+            return os.setgroups(groups)
+        except ValueError:   # error from Python's check.
+            if len(groups) <= 1:
+                raise
+            groups[:] = groups[:-1]
+        except OSError, exc:  # error from the OS.
+            if exc.errno != errno.EINVAL or len(groups) <= 1:
+                raise
+            groups[:] = groups[:-1]
+
+
+def setgroups(groups):
+    max_groups = None
+    try:
+        max_groups = os.sysconf("SC_NGROUPS_MAX")
+    except:
+        pass
+    try:
+        return _setgroups_hack(groups[:max_groups])
+    except OSError, exc:
+        if exc.errno != errno.EPERM:
+            raise
+        if any(group not in groups for group in os.getgroups()):
+            # we shouldn't be allowed to change to this group.
+            raise
+
+
+def initgroups(uid, gid):
     if grp and pwd:
-        user_name = pwd.getpwuid(uid)[0]
-        user_groups = [gr.gr_gid for gr in grp.getgrall()
-                                                if user_name in gr.gr_mem]
-        os.setgroups(user_groups)
+        username = pwd.getpwuid(uid)[0]
+        if hasattr(os, "initgroups"):  # Python 2.7+
+            return os.initgroups(username, gid)
+        groups = [gr.gr_gid for gr in grp.getgrall()
+                                if username in gr.gr_mem]
+        setgroups(groups)
 
 
 def setegid(gid):
@@ -360,15 +399,15 @@ def setuid(uid):
     os.setuid(parse_uid(uid))
 
 
-def set_effective_user(uid=None, gid=None):
+def maybe_drop_privileges(uid=None, gid=None):
     """Change process privileges to new user/group.
 
-    If UID and GID is set the effective user/group is set.
+    If UID and GID is specified, the real user/group is changed.
 
-    If only UID is set, the effective user is set, and the group is
-    set to the users primary group.
+    If only UID is specified, the real user is changed, and the group is
+    changed to the users primary group.
 
-    If only GID is set, the effective group is set.
+    If only GID is specified, only the group is changed.
 
     """
     uid = uid and parse_uid(uid)
@@ -378,8 +417,12 @@ def set_effective_user(uid=None, gid=None):
         # If GID isn't defined, get the primary GID of the user.
         if not gid and pwd:
             gid = pwd.getpwuid(uid).pw_gid
+        # Must set the GID before initgroups(), as setgid()
+        # is known to zap the group list on some platforms.
         setgid(gid)
-        setgroups(uid)
+        initgroups(uid, gid)
+
+        # at last:
         setuid(uid)
     else:
         gid and setgid(gid)