Chroot into a new dir before starting the system (#234)

* Chroot into a new dir before starting the system

Signed-off-by: Itxaka <itxaka@kairos.io>

* Use ReadDir and copy files int eh rootdir to the enw rootdir

Signed-off-by: Itxaka <itxaka@kairos.io>

* logggg

Signed-off-by: Itxaka <itxaka@kairos.io>

* Several fixes

Mount /dev at start so we can log to kmesg/ttyS0
Log more
Store the mountpoints found in root to bind them later to the new
sysroot

Signed-off-by: Itxaka <itxaka@kairos.io>

* debvuy

Signed-off-by: Itxaka <itxaka@kairos.io>

* Fix

Signed-off-by: Itxaka <itxaka@kairos.io>

* more debufg

Signed-off-by: Itxaka <itxaka@kairos.io>

* fix

Signed-off-by: Itxaka <itxaka@kairos.io>

* sfder

Signed-off-by: Itxaka <itxaka@kairos.io>

* Fix symlinks

Signed-off-by: Itxaka <itxaka@kairos.io>

* final

Signed-off-by: Itxaka <itxaka@kairos.io>

* disable mobving the / root mountpoint

Im not sure this works on our side or how, I just get errors

Signed-off-by: Itxaka <itxaka@kairos.io>

* Debug

Signed-off-by: Itxaka <itxaka@kairos.io>

* Disable remounting / as RO and enable remounting the new sysroot as RO

Signed-off-by: Itxaka <itxaka@kairos.io>

* Dont drop to bash like that

Signed-off-by: Itxaka <itxaka@kairos.io>

* Move "sysroot" to a constant, dry code and handle errors

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Fix linting errors by removing superflows `else` statements

because the `if`s end with `continue`

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Apply suggestions from code review

* Remove loggers that don't work

because `/dev` is not there yet (?). In any case, we need to switch to
the new logger

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

---------

Signed-off-by: Itxaka <itxaka@kairos.io>
Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
Co-authored-by: Dimitris Karakasilis <dimitris@karakasilis.me>
This commit is contained in:
Itxaka 2024-03-06 16:09:26 +01:00 committed by GitHub
parent a52b9651ad
commit 25975a5594
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 154 additions and 38 deletions

1
go.mod
View File

@ -114,6 +114,7 @@ require (
github.com/rivo/uniseg v0.4.4 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/samber/lo v1.37.0 // indirect
github.com/sanity-io/litter v1.5.5 // indirect
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b // indirect
github.com/sergi/go-diff v1.3.1 // indirect
github.com/shopspring/decimal v1.3.1 // indirect

4
go.sum
View File

@ -136,6 +136,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsr
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -460,6 +461,7 @@ github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qR
github.com/pkg/xattr v0.4.1/go.mod h1:W2cGD0TBEus7MkUgv0tNZ9JutLtVO3cXu+IBRuHqnFs=
github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE=
github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
@ -500,6 +502,7 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw=
github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA=
github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo=
github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM=
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
@ -539,6 +542,7 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=

View File

@ -103,6 +103,7 @@ const (
UkiDefaultcdrom = "/dev/sr0"
UkiDefaultcdromFsType = "iso9660"
UkiDefaultEfiimgFsType = "vfat"
UkiSysrootDir = "sysroot"
PersistentStateTarget = "/usr/local/.state"
LogDir = "/run/immucore"
)

View File

@ -11,6 +11,9 @@ import (
"syscall"
"time"
"github.com/sanity-io/litter"
"golang.org/x/sys/unix"
"github.com/foxboron/go-uefi/efi"
"github.com/hashicorp/go-multierror"
cnst "github.com/kairos-io/immucore/internal/constants"
@ -20,7 +23,6 @@ import (
kcrypt "github.com/kairos-io/kcrypt/pkg/lib"
"github.com/mudler/go-kdetect"
"github.com/spectrocloud-labs/herd"
"golang.org/x/sys/unix"
)
// MountTmpfsDagStep adds the step to mount /tmp .
@ -490,49 +492,18 @@ func (s *State) UKIMountBaseSystem(g *herd.Graph) error {
)
}
// UKIBootInitDagStep tries to launch /sbin/init in root and pass over the system
// booting to the real init process
// Drops to emergency if not able to. Panic if it cant even launch emergency.
func (s *State) UKIBootInitDagStep(g *herd.Graph) error {
return g.Add(cnst.OpUkiInit,
herd.WeakDeps,
herd.WithWeakDeps(cnst.OpRemountRootRO, cnst.OpRootfsHook, cnst.OpInitramfsHook, cnst.OpWriteFstab),
herd.WithCallback(func(ctx context.Context) error {
output, err := internalUtils.CommandWithPath("/usr/lib/systemd/systemd-pcrphase --graceful leave-initrd")
if err != nil {
internalUtils.Log.Err(err).Msg("running systemd-pcrphase")
internalUtils.Log.Debug().Str("out", output).Msg("systemd-pcrphase leave-initrd")
}
// Print dag before exit, otherwise its never printed as we never exit the program
internalUtils.Log.Info().Msg(s.WriteDAG(g))
internalUtils.Log.Debug().Msg("Executing init callback!")
internalUtils.CloseLogFiles()
if err := unix.Exec("/sbin/init", []string{"/sbin/init", "--system"}, os.Environ()); err != nil {
internalUtils.Log.Err(err).Msg("running init")
// drop to emergency shell
if err := unix.Exec("/bin/bash", []string{"/bin/bash"}, os.Environ()); err != nil {
internalUtils.Log.Fatal().Msg("Could not drop to emergency shell")
}
}
return nil
}))
}
// UKIRemountRootRODagStep remount root read only.
func (s *State) UKIRemountRootRODagStep(g *herd.Graph) error {
return g.Add(cnst.OpRemountRootRO,
herd.WithDeps(cnst.OpRootfsHook),
herd.WithCallback(func(ctx context.Context) error {
var err error
for i := 1; i < 5; i++ {
time.Sleep(1 * time.Second)
// Should we try to stop udev here?
err = syscall.Mount("", "/", "", syscall.MS_REMOUNT|syscall.MS_RDONLY, "")
if err != nil {
continue
}
// Create the /sysroot dir before remounting as RO
err := os.MkdirAll(s.path(cnst.UkiSysrootDir), 0755)
if err != nil {
internalUtils.Log.Err(err).Str("path", s.path(cnst.UkiSysrootDir)).Msg("Creating sysroot")
return err
}
return err
return nil
}),
)
}
@ -808,3 +779,142 @@ func (s *State) MountLiveCd(g *herd.Graph, opts ...herd.OpOption) error {
return nil
}))...)
}
// UKIBootInitDagStep tries to launch /sbin/init in root and pass over the system
// booting to the real init process
// Drops to emergency if not able to. Panic if it cant even launch emergency.
func (s *State) UKIBootInitDagStep(g *herd.Graph) error {
return g.Add(cnst.OpUkiInit,
herd.WeakDeps,
herd.WithWeakDeps(cnst.OpRemountRootRO, cnst.OpRootfsHook, cnst.OpInitramfsHook, cnst.OpWriteFstab),
herd.WithCallback(func(ctx context.Context) error {
output, err := internalUtils.CommandWithPath("/usr/lib/systemd/systemd-pcrphase --graceful leave-initrd")
if err != nil {
internalUtils.Log.Err(err).Msg("running systemd-pcrphase")
internalUtils.Log.Debug().Str("out", output).Msg("systemd-pcrphase leave-initrd")
}
// Print dag before exit, otherwise its never printed as we never exit the program
internalUtils.Log.Info().Msg(s.WriteDAG(g))
// Mount a tmpfs under sysroot
err = syscall.Mount("tmpfs", s.path(cnst.UkiSysrootDir), "tmpfs", syscall.MS_NOSUID|syscall.MS_NODEV|syscall.MS_NOEXEC, "")
if err != nil {
internalUtils.Log.Err(err).Msg("mounting tmpfs on sysroot")
}
// Move all the dirs in root FS that are not a mountpoint to the new root via Bind mount
rootDirs, err := os.ReadDir(s.Rootdir)
if err != nil {
internalUtils.Log.Err(err).Msg("reading rootdir content")
}
mountPoints := []string{}
internalUtils.Log.Debug().Str("s", litter.Sdump(rootDirs)).Msg("Moving root dirs to sysroot")
for _, file := range rootDirs {
if file.Name() == cnst.UkiSysrootDir {
continue
}
if file.IsDir() {
path := file.Name()
fileInfo, err := os.Stat(s.path(path))
if err != nil {
return err
}
parentPath := filepath.Dir(s.path(path))
parentInfo, err := os.Stat(parentPath)
if err != nil {
return err
}
// If the directory has the same device as its parent, it's not a mount point.
if fileInfo.Sys().(*syscall.Stat_t).Dev == parentInfo.Sys().(*syscall.Stat_t).Dev {
internalUtils.Log.Debug().Msg(fmt.Sprintf("%s is a simple directory", path))
err = os.MkdirAll(filepath.Join(s.path(cnst.UkiSysrootDir), path), 0755)
if err != nil {
internalUtils.Log.Err(err).Str("what", filepath.Join(s.path(cnst.UkiSysrootDir), path)).Msg("mkdir")
return err
}
// Bind mount it
err = syscall.Mount(s.path(path), filepath.Join(s.path(cnst.UkiSysrootDir), path), "", syscall.MS_BIND, "")
if err != nil {
internalUtils.Log.Err(err).Str("what", s.path(path)).
Str("where", filepath.Join(s.path(cnst.UkiSysrootDir), path)).Msg("bind mount")
return err
}
internalUtils.Log.Debug().Msg(fmt.Sprintf("Bind mounted %s to %s", s.path(path), filepath.Join(s.path(cnst.UkiSysrootDir), path)))
continue
}
internalUtils.Log.Debug().Msg(fmt.Sprintf("%s is a mount point, skipping", s.path(path)))
mountPoints = append(mountPoints, s.path(path))
continue
}
info, _ := file.Info()
fileInfo, _ := os.Lstat(file.Name())
// Symlink
if fileInfo.Mode()&os.ModeSymlink != 0 {
target, err := os.Readlink(file.Name())
if err != nil {
return fmt.Errorf("failed to read symlink: %w", err)
}
symlinkPath := s.path(filepath.Join(cnst.UkiSysrootDir, file.Name()))
err = os.Symlink(target, symlinkPath)
if err != nil {
return fmt.Errorf("failed to create symlink: %w", err)
}
internalUtils.Log.Debug().Str("from", target).Str("to", symlinkPath).Msg("Symlinked file")
} else {
// If its a file in the root dir just copy it over
content, _ := os.ReadFile(s.path(file.Name()))
newFilePath := s.path(filepath.Join(cnst.UkiSysrootDir, file.Name()))
_ = os.WriteFile(newFilePath, content, info.Mode())
internalUtils.Log.Debug().Msg(fmt.Sprintf("Copied %s to %s", s.path(file.Name()), newFilePath))
}
}
// Now move the system mounts into the new dir
for _, d := range mountPoints {
newDir := filepath.Join(s.path(cnst.UkiSysrootDir), d)
err := os.MkdirAll(newDir, 0755)
if err != nil {
internalUtils.Log.Err(err).Str("what", newDir).Msg("mkdir")
}
err = syscall.Mount(filepath.Join(s.path(), d), newDir, "", syscall.MS_MOVE, "")
if err != nil {
internalUtils.Log.Err(err).Str("what", filepath.Join(s.Rootdir, d)).Str("where", newDir).Msg("move mount")
continue
}
internalUtils.Log.Debug().Str("from", filepath.Join(s.path(), d)).Str("to", newDir).Msg("Mount moved")
}
// remount sysroot as readonly before chrooting
if err = syscall.Mount("", s.path(cnst.UkiSysrootDir), "", syscall.MS_REMOUNT|syscall.MS_RDONLY, ""); err != nil {
internalUtils.Log.Err(err).Msg("re-mounting sysroot as read only")
}
// Now chdir+chroot into the new dir
if err := unix.Chdir(s.path(cnst.UkiSysrootDir)); err != nil {
internalUtils.Log.Err(err).Msg("chdir")
return fmt.Errorf("failed change directory to new_root %v", err)
}
internalUtils.Log.Debug().Msg("Chdir to sysroot done")
err = unix.Chroot(".")
if err != nil {
internalUtils.Log.Err(err).Msg("chroot")
return fmt.Errorf("failed to chroot %v", err)
}
internalUtils.Log.Debug().Msg("Chroot to sysroot done")
internalUtils.Log.Debug().Msg("Executing init callback!")
if err := unix.Exec("/sbin/init", []string{"/sbin/init"}, os.Environ()); err != nil {
internalUtils.Log.Err(err).Msg("running init")
// drop to emergency shell
if err := unix.Exec("/bin/bash", []string{"/bin/bash"}, os.Environ()); err != nil {
internalUtils.Log.Fatal().Msg("Could not drop to emergency shell")
}
}
return nil
}))
}