Skip to content

Commit 69af385

Browse files
committed
libcontainer: user: always treat numeric ids numerically
Most shadow-related tools don't treat numeric ids as potential usernames, so change our behaviour to match that. Previously, using an explicit specification like 111:222 could result in the UID and GID not being 111 and 222 respectively (which is confusing). Signed-off-by: Aleksa Sarai <asarai@suse.de>
1 parent 8fa5343 commit 69af385

File tree

1 file changed

+57
-32
lines changed

1 file changed

+57
-32
lines changed

libcontainer/user/user.go

Lines changed: 57 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -235,10 +235,14 @@ func GetExecUserPath(userSpec string, defaults *ExecUser, passwdPath, groupPath
235235
// * "uid:gid
236236
// * "user:gid"
237237
// * "uid:group"
238+
//
239+
// It should be noted that if you specify a numeric user or group id, they will
240+
// not be evaluated as usernames (only the metadata will be filled). So attempting
241+
// to parse a user with user.Name = "1337" will produce the user with a UID of
242+
// 1337.
238243
func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (*ExecUser, error) {
239244
var (
240245
userArg, groupArg string
241-
name string
242246
)
243247

244248
if defaults == nil {
@@ -261,11 +265,22 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (
261265
// allow for userArg to have either "user" syntax, or optionally "user:group" syntax
262266
parseLine(userSpec, &userArg, &groupArg)
263267

268+
// Convert userArg and groupArg to be numeric, so we don't have to execute
269+
// Atoi *twice* for each iteration over lines.
270+
uidArg, uidErr := strconv.Atoi(userArg)
271+
gidArg, gidErr := strconv.Atoi(groupArg)
272+
264273
users, err := ParsePasswdFilter(passwd, func(u User) bool {
265274
if userArg == "" {
266275
return u.Uid == user.Uid
267276
}
268-
return u.Name == userArg || strconv.Itoa(u.Uid) == userArg
277+
278+
if uidErr == nil {
279+
// If the userArg is numeric, always treat it as a UID.
280+
return uidArg == u.Uid
281+
}
282+
283+
return u.Name == userArg
269284
})
270285
if err != nil && passwd != nil {
271286
if userArg == "" {
@@ -274,71 +289,81 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (
274289
return nil, fmt.Errorf("Unable to find user %v: %v", userArg, err)
275290
}
276291

277-
haveUser := users != nil && len(users) > 0
278-
if haveUser {
279-
// if we found any user entries that matched our filter, let's take the first one as "correct"
280-
name = users[0].Name
292+
var matchedUserName string
293+
if len(users) > 0 {
294+
// First match wins, even if there's more than one matching entry.
295+
matchedUserName = users[0].Name
281296
user.Uid = users[0].Uid
282297
user.Gid = users[0].Gid
283298
user.Home = users[0].Home
284299
} else if userArg != "" {
285-
// we asked for a user but didn't find them... let's check to see if we wanted a numeric user
286-
user.Uid, err = strconv.Atoi(userArg)
287-
if err != nil {
288-
// not numeric - we have to bail
289-
return nil, fmt.Errorf("Unable to find user %v", userArg)
300+
// If we can't find a user with the given username, the only other valid
301+
// option is if it's a numeric username with no associated entry in passwd.
302+
303+
if uidErr != nil {
304+
// Not numeric.
305+
return nil, fmt.Errorf("unable to find user %s: %v", userArg, ErrNoPasswdEntries)
290306
}
307+
user.Uid = uidArg
291308

292309
// Must be inside valid uid range.
293310
if user.Uid < minId || user.Uid > maxId {
294311
return nil, ErrRange
295312
}
296313

297-
// if userArg couldn't be found in /etc/passwd but is numeric, just roll with it - this is legit
314+
// Okay, so it's numeric. We can just roll with this.
298315
}
299316

300-
if groupArg != "" || name != "" {
317+
// On to the groups. If we matched a username, we need to do this because of
318+
// the supplementary group IDs.
319+
if groupArg != "" || matchedUserName != "" {
301320
groups, err := ParseGroupFilter(group, func(g Group) bool {
302-
// Explicit group format takes precedence.
303-
if groupArg != "" {
304-
return g.Name == groupArg || strconv.Itoa(g.Gid) == groupArg
321+
// If the group argument isn't explicit, we'll just search for it.
322+
if groupArg == "" {
323+
// Check if user is a member of this group.
324+
for _, u := range g.List {
325+
if u == matchedUserName {
326+
return true
327+
}
328+
}
329+
return false
305330
}
306331

307-
// Check if user is a member.
308-
for _, u := range g.List {
309-
if u == name {
310-
return true
311-
}
332+
if gidErr == nil {
333+
// If the groupArg is numeric, always treat it as a GID.
334+
return gidArg == g.Gid
312335
}
313336

314-
return false
337+
return g.Name == groupArg
315338
})
316339
if err != nil && group != nil {
317-
return nil, fmt.Errorf("Unable to find groups for user %v: %v", users[0].Name, err)
340+
return nil, fmt.Errorf("unable to find groups for spec %v: %v", matchedUserName, err)
318341
}
319342

320343
haveGroup := groups != nil && len(groups) > 0
321344
if groupArg != "" {
322345
if haveGroup {
323346
// if we found any group entries that matched our filter, let's take the first one as "correct"
324347
user.Gid = groups[0].Gid
325-
} else {
326-
// we asked for a group but didn't find id... let's check to see if we wanted a numeric group
327-
user.Gid, err = strconv.Atoi(groupArg)
328-
if err != nil {
329-
// not numeric - we have to bail
330-
return nil, fmt.Errorf("Unable to find group %v", groupArg)
348+
} else if groupArg != "" {
349+
// If we can't find a group with the given name, the only other valid
350+
// option is if it's a numeric group name with no associated entry in group.
351+
352+
if gidErr != nil {
353+
// Not numeric.
354+
return nil, fmt.Errorf("unable to find group %s: %v", groupArg, ErrNoGroupEntries)
331355
}
356+
user.Gid = gidArg
332357

333358
// Ensure gid is inside gid range.
334359
if user.Gid < minId || user.Gid > maxId {
335360
return nil, ErrRange
336361
}
337362

338-
// if groupArg couldn't be found in /etc/group but is numeric, just roll with it - this is legit
363+
// Okay, so it's numeric. We can just roll with this.
339364
}
340-
} else if haveGroup {
341-
// If implicit group format, fill supplementary gids.
365+
} else if len(groups) > 0 {
366+
// Supplementary group ids only make sense if in the implicit form.
342367
user.Sgids = make([]int, len(groups))
343368
for i, group := range groups {
344369
user.Sgids[i] = group.Gid

0 commit comments

Comments
 (0)