Initial operating system work
Adds a draft for most of the operating system work, sans external things like EDK2 and kernel build which will be pushed later in a separate diff.
* Sealing/Unsealing of encrypted and integrity-protected data partition using TPM2
* PID1 standard behaviour (mounting minimal filesystems, cleaning up orphans)
* TPM2.0 helper library
* Block device finding and mounting
Test Plan: Manually tested, CI will be dealt with later.
X-Origin-Diff: phab/D157
GitOrigin-RevId: 6fc494f50cab1f081c3d352677158c009f4d7990
diff --git a/cmd/init/main.go b/cmd/init/main.go
new file mode 100644
index 0000000..1477109
--- /dev/null
+++ b/cmd/init/main.go
@@ -0,0 +1,158 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "os/signal"
+ node2 "smalltown/internal/node"
+ "smalltown/internal/storage"
+ "smalltown/pkg/tpm"
+ "syscall"
+
+ "go.uber.org/zap"
+ "golang.org/x/sys/unix"
+)
+
+func main() {
+ logger, err := zap.NewDevelopment()
+ if err != nil {
+ panic(err)
+ }
+ logger.Info("Starting Smalltown Init")
+
+ // Set up bare minimum mounts
+ if err := os.Mkdir("/sys", 0755); err != nil {
+ panic(err)
+ }
+ if err := unix.Mount("sysfs", "/sys", "sysfs", unix.MS_NOEXEC|unix.MS_NOSUID|unix.MS_NODEV, ""); err != nil {
+ panic(err)
+ }
+
+ if err := os.Mkdir("/proc", 0755); err != nil {
+ panic(err)
+ }
+ if err := unix.Mount("procfs", "/proc", "proc", unix.MS_NOEXEC|unix.MS_NOSUID|unix.MS_NODEV, ""); err != nil {
+ panic(err)
+ }
+
+ signalChannel := make(chan os.Signal, 2)
+ signal.Notify(signalChannel)
+
+ if err := storage.FindPartitions(); err != nil {
+ logger.Panic("Failed to search for partitions", zap.Error(err))
+ }
+
+ if err := os.Mkdir("/esp", 0755); err != nil {
+ panic(err)
+ }
+
+ if err := unix.Mount(storage.ESPDevicePath, "/esp", "vfat", unix.MS_NOEXEC|unix.MS_NODEV|unix.MS_SYNC, ""); err != nil {
+ logger.Panic("Failed to mount ESP partition", zap.Error(err))
+ }
+
+ if err := tpm.Initialize(logger.With(zap.String("component", "tpm"))); err != nil {
+ logger.Panic("Failed to initialize TPM 2.0", zap.Error(err))
+ }
+
+ // TODO(lorenz): This really doesn't belong here and needs to be asynchronous as well
+ var keyLocation = "/esp/EFI/smalltown/data-key.bin"
+ sealedKeyFile, err := os.Open(keyLocation)
+ if os.IsNotExist(err) {
+ logger.Info("Initializing encrypted storage, this might take a while...")
+ key, err := tpm.GenerateSafeKey(256 / 8)
+ if err != nil {
+ panic(err)
+ }
+ sealedKey, err := tpm.Seal(key, tpm.SecureBootPCRs)
+ if err != nil {
+ panic(err)
+ }
+ if err := storage.InitializeEncryptedBlockDevice("data", storage.SmalltownDataCryptPath, key); err != nil {
+ panic(err)
+ }
+ mkfsCmd := exec.Command("/bin/mkfs.xfs", "-qf", "/dev/data")
+ if _, err := mkfsCmd.Output(); err != nil {
+ panic(err)
+ }
+ // Existence of this file indicates that the encrypted storage has been successfully initialized
+ if err := ioutil.WriteFile(keyLocation, sealedKey, 0600); err != nil {
+ panic(err)
+ }
+ logger.Info("Initialized encrypted storage")
+ } else if err != nil {
+ panic(err)
+ } else {
+ sealedKey, err := ioutil.ReadAll(sealedKeyFile)
+ if err != nil {
+ panic(err)
+ }
+ key, err := tpm.Unseal(sealedKey)
+ if err != nil {
+ panic(err)
+ }
+ if err := storage.MapEncryptedBlockDevice("data", storage.SmalltownDataCryptPath, key); err != nil {
+ panic(err)
+ }
+ logger.Info("Opened encrypted storage")
+ }
+ sealedKeyFile.Close()
+
+ if err := os.Mkdir("/data", 0755); err != nil {
+ panic(err)
+ }
+
+ if err := unix.Mount("/dev/data", "/data", "xfs", unix.MS_NOEXEC|unix.MS_NODEV, ""); err != nil {
+ panic(err)
+ }
+
+ node, err := node2.NewSmalltownNode(logger, "/esp/EFI/smalltown", "/data", 7833, 7834)
+ if err != nil {
+ panic(err)
+ }
+
+ err = node.Start()
+ if err != nil {
+ panic(err)
+ }
+
+ // We're PID1, so orphaned processes get reparented to us to clean up
+ for {
+ sig := <-signalChannel
+ switch sig {
+ case unix.SIGCHLD:
+ var status unix.WaitStatus
+ var rusage unix.Rusage
+ for {
+ res, err := unix.Wait4(-1, &status, syscall.WNOHANG, &rusage)
+ if err != nil {
+ logger.Error("Failed to wait on orphaned child", zap.Error(err))
+ break
+ }
+ if res <= 0 {
+ break
+ }
+ }
+ // TODO(lorenz): We can probably get more than just SIGCHLD as init, but I can't think
+ // of any others right now, just log them in case we hit any of them.
+ default:
+ logger.Warn("Got unexpected signal", zap.String("signal", sig.String()))
+ }
+ }
+}
diff --git a/cmd/mkimage/main.go b/cmd/mkimage/main.go
new file mode 100644
index 0000000..e6c9c58
--- /dev/null
+++ b/cmd/mkimage/main.go
@@ -0,0 +1,128 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "smalltown/generated/common"
+ "smalltown/internal/config"
+
+ "github.com/diskfs/go-diskfs"
+ "github.com/diskfs/go-diskfs/disk"
+ "github.com/diskfs/go-diskfs/filesystem"
+ "github.com/diskfs/go-diskfs/partition/gpt"
+ "github.com/naoina/toml"
+)
+
+var SmalltownDataPartition gpt.Type = gpt.Type("9eeec464-6885-414a-b278-4305c51f7966")
+
+func mibToSectors(size uint64) uint64 {
+ return (size * 1024 * 1024) / 512
+}
+
+var cfg = config.Config{
+ NodeName: "smalltown-testing",
+ DataDirectory: "/data",
+ ExternalHost: "",
+ TrustBackend: common.TrustBackend_DUMMY,
+}
+
+func main() {
+ os.Remove("smalltown.img")
+ diskImg, err := diskfs.Create("smalltown.img", 3*1024*1024*1024, diskfs.Raw)
+ if err != nil {
+ fmt.Printf("Failed to create disk: %v", err)
+ os.Exit(1)
+ }
+
+ table := &gpt.Table{
+ // This is appropriate at least for virtio disks. Might need to be adjusted for real ones.
+ LogicalSectorSize: 512,
+ PhysicalSectorSize: 512,
+ ProtectiveMBR: true,
+ Partitions: []*gpt.Partition{
+ {
+ Type: gpt.EFISystemPartition,
+ Name: "ESP",
+ Start: mibToSectors(1),
+ End: mibToSectors(128) - 1,
+ },
+ {
+ Type: SmalltownDataPartition,
+ Name: "SIGNOS-DATA",
+ Start: mibToSectors(128),
+ End: mibToSectors(2560) - 1,
+ },
+ },
+ }
+ if err := diskImg.Partition(table); err != nil {
+ fmt.Printf("Failed to apply partition table: %v", err)
+ os.Exit(1)
+ }
+
+ fs, err := diskImg.CreateFilesystem(disk.FilesystemSpec{Partition: 1, FSType: filesystem.TypeFat32, VolumeLabel: "ESP"})
+ if err != nil {
+ fmt.Printf("Failed to create filesystem: %v", err)
+ os.Exit(1)
+ }
+ if err := fs.Mkdir("/EFI"); err != nil {
+ panic(err)
+ }
+ if err := fs.Mkdir("/EFI/BOOT"); err != nil {
+ panic(err)
+ }
+ if err := fs.Mkdir("/EFI/smalltown"); err != nil {
+ panic(err)
+ }
+ efiPayload, err := fs.OpenFile("/EFI/BOOT/BOOTX64.EFI", os.O_CREATE|os.O_RDWR)
+ if err != nil {
+ fmt.Printf("Failed to open EFI payload for writing: %v", err)
+ os.Exit(1)
+ }
+ efiPayloadSrc, err := os.Open(os.Args[1])
+ if err != nil {
+ fmt.Printf("Failed to open EFI payload for reading: %v", err)
+ os.Exit(1)
+ }
+ defer efiPayloadSrc.Close()
+ // If this is streamed (e.g. using io.Copy) it exposes a bug in diskfs, so do it in one go.
+ efiPayloadFull, err := ioutil.ReadAll(efiPayloadSrc)
+ if err != nil {
+ panic(err)
+ }
+ if _, err := efiPayload.Write(efiPayloadFull); err != nil {
+ fmt.Printf("Failed to write EFI payload: %v", err)
+ os.Exit(1)
+ }
+ configFile, err := fs.OpenFile("/EFI/smalltown/config.toml", os.O_CREATE|os.O_RDWR)
+ if err != nil {
+ fmt.Printf("Failed to open config for writing: %v", err)
+ os.Exit(1)
+ }
+ configData, _ := toml.Marshal(cfg)
+ if _, err := configFile.Write(configData); err != nil {
+ fmt.Printf("Failed to write config: %v", err)
+ os.Exit(1)
+ }
+ if err := diskImg.File.Close(); err != nil {
+ fmt.Printf("Failed to write image: %v", err)
+ os.Exit(1)
+ }
+ fmt.Println("Success! You can now boot smalltown.img")
+}