Skip to content

Commit 03960ea

Browse files
authored
[rqd] Fix permission issues when becoming a user (#1496)
Changes implemented by #1416 impacted the locking mechanism for handling permissions on rqd, causing multiple threads to compete for permission settings and access to passwords. Besides fixing the bug, this PR also introduces a fix for a potential security issue that would allow frames to run as root if the frame user didn't exist and the process to create this user fails.
1 parent 61a976f commit 03960ea

File tree

2 files changed

+42
-39
lines changed

2 files changed

+42
-39
lines changed

rqd/rqd/rqcore.py

+10-14
Original file line numberDiff line numberDiff line change
@@ -295,10 +295,6 @@ def runLinux(self):
295295

296296
self.__createEnvVariables()
297297
self.__writeHeader()
298-
if rqd.rqconstants.RQD_CREATE_USER_IF_NOT_EXISTS:
299-
rqd.rqutil.permissionsHigh()
300-
rqd.rqutil.checkAndCreateUser(runFrame.user_name, runFrame.uid, runFrame.gid)
301-
rqd.rqutil.permissionsLow()
302298

303299
tempStatFile = "%srqd-stat-%s-%s" % (self.rqCore.machine.getTempPath(),
304300
frameInfo.frameId,
@@ -508,17 +504,17 @@ def run(self):
508504
runFrame.log_dir_file = os.path.join(runFrame.log_dir, runFrame.log_file)
509505

510506
try: # Exception block for all exceptions
511-
512-
# Change to frame user if needed:
513-
if runFrame.HasField("uid"):
514-
# Do everything as launching user:
515-
runFrame.gid = rqd.rqconstants.LAUNCH_FRAME_USER_GID
516-
rqd.rqutil.permissionsUser(runFrame.uid, runFrame.gid)
517-
507+
# Ensure permissions return to Low after this block
518508
try:
519-
#
520-
# Setup proc to allow launching of frame
521-
#
509+
if rqd.rqconstants.RQD_CREATE_USER_IF_NOT_EXISTS and runFrame.HasField("uid"):
510+
rqd.rqutil.checkAndCreateUser(runFrame.user_name,
511+
runFrame.uid,
512+
runFrame.gid)
513+
# Do everything as launching user:
514+
runFrame.gid = rqd.rqconstants.LAUNCH_FRAME_USER_GID
515+
rqd.rqutil.permissionsUser(runFrame.uid, runFrame.gid)
516+
517+
# Setup frame logging
522518
try:
523519
self.rqlog = rqd.rqlogging.RqdLogger(runFrame.log_dir_file)
524520
self.rqlog.waitForFile()

rqd/rqd/rqutil.py

+32-25
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,17 @@ def permissionsHigh():
7979
"""Sets the effective gid/uid to processes original values (root)"""
8080
if platform.system() == "Windows" or not rqd.rqconstants.RQD_BECOME_JOB_USER:
8181
return
82-
with PERMISSIONS:
83-
os.setegid(os.getgid())
84-
os.seteuid(os.getuid())
85-
try:
86-
os.setgroups(HIGH_PERMISSION_GROUPS)
87-
# pylint: disable=broad-except
88-
except Exception:
89-
pass
82+
# PERMISSIONS gets locked here and unlocked at permissionsLow()
83+
# therefore 'with' should not be used here
84+
# pylint: disable=consider-using-with
85+
PERMISSIONS.acquire()
86+
os.setegid(os.getgid())
87+
os.seteuid(os.getuid())
88+
try:
89+
os.setgroups(HIGH_PERMISSION_GROUPS)
90+
# pylint: disable=broad-except
91+
except Exception:
92+
pass
9093

9194

9295
def permissionsLow():
@@ -114,10 +117,9 @@ def permissionsUser(uid, gid):
114117
groups = [20] + [g.gr_gid for g in grp.getgrall() if username in g.gr_mem]
115118
os.setgroups(groups)
116119
# pylint: disable=broad-except
117-
except Exception:
118-
pass
119-
os.setegid(gid)
120-
os.seteuid(uid)
120+
finally:
121+
os.setegid(gid)
122+
os.seteuid(uid)
121123

122124

123125
def __becomeRoot():
@@ -134,24 +136,29 @@ def __becomeRoot():
134136

135137
def checkAndCreateUser(username, uid=None, gid=None):
136138
"""Check to see if the provided user exists, if not attempt to create it."""
137-
# TODO(gregdenton): Add Windows and Mac support here. (Issue #61)
138-
if not rqd.rqconstants.RQD_BECOME_JOB_USER:
139+
if platform.system() == "Windows" or not rqd.rqconstants.RQD_BECOME_JOB_USER:
139140
return
140141
try:
141142
pwd.getpwnam(username)
142143
return
143144
except KeyError:
144-
cmd = [
145-
'useradd',
146-
'-p', str(uuid.uuid4()), # generate a random password
147-
]
148-
if uid:
149-
cmd += ['-u', str(uid)]
150-
if gid:
151-
cmd += ['-g', str(gid)]
152-
cmd.append(username)
153-
log.info("Frame's username not found on host. Adding user with: %s", cmd)
154-
subprocess.check_call(cmd)
145+
# Multiple processes can be trying to access passwd, permissionHigh and
146+
# permissionLow handle locking
147+
permissionsHigh()
148+
try:
149+
cmd = [
150+
'useradd',
151+
'-p', str(uuid.uuid4()), # generate a random password
152+
]
153+
if uid:
154+
cmd += ['-u', str(uid)]
155+
if gid:
156+
cmd += ['-g', str(gid)]
157+
cmd.append(username)
158+
log.info("Frame's username not found on host. Adding user with: %s", cmd)
159+
subprocess.check_call(cmd)
160+
finally:
161+
permissionsLow()
155162

156163

157164
def getHostIp():

0 commit comments

Comments
 (0)