🐛 Fix chroot unmounting (#80)

Signed-off-by: Itxaka <itxaka.garcia@spectrocloud.com>
This commit is contained in:
Itxaka
2023-03-08 19:44:45 +01:00
committed by GitHub
parent aa5939da89
commit 41fcc3c03b
2 changed files with 35 additions and 25 deletions

View File

@@ -36,17 +36,14 @@ type Chroot struct {
func NewChroot(path string) *Chroot { func NewChroot(path string) *Chroot {
return &Chroot{ return &Chroot{
path: path, path: path,
defaultMounts: []string{"/dev", "/proc", "/sys", "/run", "/tmp"}, defaultMounts: []string{
"/sys", "/dev", "/dev/pts", "/dev/shm", "/proc", "/tmp",
"/run/rootfsbase", "/run/initramfs/live", "/run",
},
activeMounts: []string{}, activeMounts: []string{},
} }
} }
// ChrootedCallback runs the given callback in a chroot environment.
func ChrootedCallback(path string, bindMounts map[string]string, callback func() error) error {
chroot := NewChroot(path)
return chroot.RunCallback(callback)
}
// Prepare will mount the defaultMounts as bind mounts, to be ready when we run chroot. // Prepare will mount the defaultMounts as bind mounts, to be ready when we run chroot.
func (c *Chroot) Prepare() error { func (c *Chroot) Prepare() error {
var err error var err error
@@ -55,20 +52,35 @@ func (c *Chroot) Prepare() error {
return errors.New("there are already active mountpoints for this instance") return errors.New("there are already active mountpoints for this instance")
} }
defer func() {
if err != nil {
c.Close()
}
}()
for _, mnt := range c.defaultMounts { for _, mnt := range c.defaultMounts {
mountPoint := filepath.Join(c.path, mnt) mountPoint := filepath.Join(c.path, mnt)
if _, err := os.Stat(mnt); os.IsNotExist(err) {
// Source doesnt exist, skip it
Log.Debug().Str("what", mnt).Msg("Source does not exists, not mounting in chroot")
continue
}
err = CreateIfNotExists(mountPoint) err = CreateIfNotExists(mountPoint)
if err != nil { if err != nil {
Log.Err(err).Str("what", mountPoint).Msg("Creating dir") Log.Err(err).Str("what", mountPoint).Msg("Creating dir")
return err return err
} }
err = syscall.Mount(mnt, mountPoint, "bind", syscall.MS_BIND|syscall.MS_REC, "") // Don't mount /sys, /dev or /run as MS_REC as this brings a lot of submounts for cgroups and such and those are not needed
// and prevents up from cleaning up the chroot afterwards
// For example you can also have a cdrom device mounted under /dev/sr0 or /dev/cdrom and we dont know how to find it and mark it private
switch {
case mnt == "/sys", mnt == "/dev", mnt == "/run":
err = syscall.Mount(mnt, mountPoint, "", syscall.MS_BIND, "")
default:
err = syscall.Mount(mnt, mountPoint, "", syscall.MS_BIND|syscall.MS_REC, "")
}
if err != nil {
Log.Err(err).Str("where", mountPoint).Str("what", mnt).Msg("Mounting chroot bind")
return err
}
// "remount" with private so unmount events do not propagate
err = syscall.Mount("", mountPoint, "", syscall.MS_PRIVATE, "")
if err != nil { if err != nil {
Log.Err(err).Str("where", mountPoint).Str("what", mnt).Msg("Mounting chroot bind") Log.Err(err).Str("where", mountPoint).Str("what", mnt).Msg("Mounting chroot bind")
return err return err
@@ -82,6 +94,9 @@ func (c *Chroot) Prepare() error {
// Close will unmount all active mounts created in Prepare on reverse order. // Close will unmount all active mounts created in Prepare on reverse order.
func (c *Chroot) Close() error { func (c *Chroot) Close() error {
failures := []string{} failures := []string{}
Log.Debug().Strs("activeMounts", c.activeMounts).Msg("Closing chroot")
// Something mounts this due to selinux, so we need to try to manually unmount and ignore any errors
_ = syscall.Unmount(filepath.Join(c.path, "/sys/fs/selinux"), 0)
for len(c.activeMounts) > 0 { for len(c.activeMounts) > 0 {
curr := c.activeMounts[len(c.activeMounts)-1] curr := c.activeMounts[len(c.activeMounts)-1]
Log.Debug().Str("what", curr).Msg("Unmounting from chroot") Log.Debug().Str("what", curr).Msg("Unmounting from chroot")
@@ -131,12 +146,9 @@ func (c *Chroot) RunCallback(callback func() error) (err error) {
Log.Err(err).Msg("Can't mount default mounts") Log.Err(err).Msg("Can't mount default mounts")
return err return err
} }
defer func() { defer func(c *Chroot) {
tmpErr := c.Close() err = c.Close()
if err == nil { }(c)
err = tmpErr
}
}()
} }
// Change to new dir before running chroot! // Change to new dir before running chroot!
err = syscall.Chdir(c.path) err = syscall.Chdir(c.path)

View File

@@ -95,7 +95,6 @@ func (s *State) RunStageOp(stage string) func(context.Context) error {
} }
output, err := utils.SH(cmd) output, err := utils.SH(cmd)
internalUtils.Log.Info().Msg("Running rootfs stage") internalUtils.Log.Info().Msg("Running rootfs stage")
internalUtils.Log.Info().Msg(output)
f, ferr := os.Create(filepath.Join(constants.LogDir, "rootfs_stage.log")) f, ferr := os.Create(filepath.Join(constants.LogDir, "rootfs_stage.log"))
if ferr == nil { if ferr == nil {
_, _ = f.WriteString(output) _, _ = f.WriteString(output)
@@ -104,10 +103,9 @@ func (s *State) RunStageOp(stage string) func(context.Context) error {
return err return err
case "initramfs": case "initramfs":
// Not sure if it will work under UKI where the s.Rootdir is the current root already // Not sure if it will work under UKI where the s.Rootdir is the current root already
internalUtils.Log.Info().Msg("Running initramfs stage")
chroot := internalUtils.NewChroot(s.Rootdir) chroot := internalUtils.NewChroot(s.Rootdir)
output, err := chroot.Run(cmd) output, err := chroot.Run(cmd)
internalUtils.Log.Info().Msg("Running initramfs stage")
internalUtils.Log.Info().Msg(output)
f, ferr := os.Create(filepath.Join(constants.LogDir, "initramfs_stage.log")) f, ferr := os.Create(filepath.Join(constants.LogDir, "initramfs_stage.log"))
if ferr == nil { if ferr == nil {
_, _ = f.WriteString(output) _, _ = f.WriteString(output)