mirror of
https://github.com/moby/buildkit.git
synced 2026-08-09 01:01:08 +00:00
Resolving a username to uid/gid read /etc/passwd and /etc/group via os.Open with no upper bound, letting a crafted image force unbounded memory use during user resolution. Cap reads at 10MiB and reject non-regular files in both the OCI executor and the chown user resolver. Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com> (cherry picked from commit 83cfc1e0ea1dcf8816f259ee6720b8694ab874e5)
162 lines
3.3 KiB
Go
162 lines
3.3 KiB
Go
package ops
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
"syscall"
|
|
|
|
"github.com/containerd/continuity/fs"
|
|
"github.com/moby/buildkit/snapshot"
|
|
"github.com/moby/buildkit/solver/pb"
|
|
"github.com/moby/buildkit/worker"
|
|
"github.com/moby/sys/user"
|
|
"github.com/pkg/errors"
|
|
copy "github.com/tonistiigi/fsutil/copy"
|
|
)
|
|
|
|
const maxUserFileBytes = 10 << 20
|
|
|
|
func getReadUserFn(_ worker.Worker) func(chopt *pb.ChownOpt, mu, mg snapshot.Mountable) (*copy.User, error) {
|
|
return readUser
|
|
}
|
|
|
|
func readUser(chopt *pb.ChownOpt, mu, mg snapshot.Mountable) (*copy.User, error) {
|
|
if chopt == nil {
|
|
return nil, nil
|
|
}
|
|
var us copy.User
|
|
if chopt.User != nil {
|
|
switch u := chopt.User.User.(type) {
|
|
case *pb.UserOpt_ByName:
|
|
if mu == nil {
|
|
return nil, errors.Errorf("invalid missing user mount")
|
|
}
|
|
|
|
lm := snapshot.LocalMounter(mu)
|
|
dir, err := lm.Mount()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer lm.Unmount()
|
|
|
|
passwdPath, err := user.GetPasswdPath()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ufile, err := openUserFile(dir, passwdPath)
|
|
if errors.Is(err, os.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
|
|
// Couldn't open the file. Considering this case as not finding the user in the file.
|
|
break
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer ufile.Close()
|
|
|
|
users, err := user.ParsePasswdFilter(ufile, func(uu user.User) bool {
|
|
return uu.Name == u.ByName.Name
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(users) > 0 {
|
|
us.UID = users[0].Uid
|
|
us.GID = users[0].Gid
|
|
}
|
|
case *pb.UserOpt_ByID:
|
|
us.UID = int(u.ByID)
|
|
us.GID = int(u.ByID)
|
|
}
|
|
}
|
|
|
|
if chopt.Group != nil {
|
|
switch u := chopt.Group.User.(type) {
|
|
case *pb.UserOpt_ByName:
|
|
if mg == nil {
|
|
return nil, errors.Errorf("invalid missing group mount")
|
|
}
|
|
|
|
lm := snapshot.LocalMounter(mg)
|
|
dir, err := lm.Mount()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer lm.Unmount()
|
|
|
|
groupPath, err := user.GetGroupPath()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
gfile, err := openUserFile(dir, groupPath)
|
|
if errors.Is(err, os.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
|
|
// Couldn't open the file. Considering this case as not finding the group in the file.
|
|
break
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer gfile.Close()
|
|
|
|
groups, err := user.ParseGroupFilter(gfile, func(g user.Group) bool {
|
|
return g.Name == u.ByName.Name
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(groups) > 0 {
|
|
us.GID = groups[0].Gid
|
|
}
|
|
case *pb.UserOpt_ByID:
|
|
us.GID = int(u.ByID)
|
|
}
|
|
}
|
|
|
|
return &us, nil
|
|
}
|
|
|
|
func openUserFile(root, p string) (io.ReadCloser, error) {
|
|
p, err := fs.RootPath(root, p)
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
f, err := os.Open(p)
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
info, err := f.Stat()
|
|
if err != nil {
|
|
f.Close()
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
if !info.Mode().IsRegular() {
|
|
f.Close()
|
|
return nil, errors.Errorf("%s is not a regular file", p)
|
|
}
|
|
|
|
return &limitedReadCloser{
|
|
ReadCloser: f,
|
|
r: &io.LimitedReader{R: f, N: maxUserFileBytes + 1},
|
|
name: p,
|
|
}, nil
|
|
}
|
|
|
|
type limitedReadCloser struct {
|
|
io.ReadCloser
|
|
r *io.LimitedReader
|
|
name string
|
|
}
|
|
|
|
func (l *limitedReadCloser) Read(p []byte) (int, error) {
|
|
n, err := l.r.Read(p)
|
|
if l.r.N == 0 {
|
|
return n, errors.Errorf("%q exceeds %d bytes", l.name, maxUserFileBytes)
|
|
}
|
|
return n, err
|
|
}
|