Skip to content

Commit

Permalink
sbctl: implement landlock sandboxing
Browse files Browse the repository at this point in the history
Signed-off-by: Morten Linderud <[email protected]>
  • Loading branch information
Foxboron committed Jul 29, 2024
1 parent 1454913 commit 009e248
Show file tree
Hide file tree
Showing 21 changed files with 259 additions and 7 deletions.
6 changes: 6 additions & 0 deletions cmd/sbctl/create-keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/foxboron/sbctl/backend"
"github.com/foxboron/sbctl/config"
"github.com/foxboron/sbctl/logging"
"github.com/foxboron/sbctl/lsm"
"github.com/spf13/cobra"
)

Expand All @@ -28,6 +29,11 @@ var createKeysCmd = &cobra.Command{
}

func RunCreateKeys(state *config.State) error {
if state.Config.Landlock {
if err := lsm.Restrict(); err != nil {
return err
}
}
// Overrides keydir or GUID location
if exportPath != "" {
state.Config.Keydir = exportPath
Expand Down
6 changes: 6 additions & 0 deletions cmd/sbctl/enroll-keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/foxboron/sbctl/config"
"github.com/foxboron/sbctl/fs"
"github.com/foxboron/sbctl/logging"
"github.com/foxboron/sbctl/lsm"
"github.com/foxboron/sbctl/stringset"
"github.com/spf13/afero"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -60,6 +61,11 @@ var (
Short: "Enroll the current keys to EFI",
RunE: func(cmd *cobra.Command, args []string) error {
state := cmd.Context().Value(stateDataKey{}).(*config.State)
if state.Config.Landlock {
if err := lsm.Restrict(); err != nil {
return err
}
}
return RunEnrollKeys(state)
},
}
Expand Down
13 changes: 13 additions & 0 deletions cmd/sbctl/export-enrolled-keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import (

"github.com/foxboron/go-uefi/efi"
"github.com/foxboron/go-uefi/efi/signature"
"github.com/foxboron/sbctl/config"
"github.com/foxboron/sbctl/lsm"
"github.com/landlock-lsm/go-landlock/landlock"
"github.com/spf13/cobra"
)

Expand All @@ -20,10 +23,20 @@ var exportEnrolledKeysCmd = &cobra.Command{
Use: "export-enrolled-keys",
Short: "Export already enrolled keys from the system",
RunE: func(cmd *cobra.Command, args []string) error {
state := cmd.Context().Value(stateDataKey{}).(*config.State)

if exportDir == "" {
fmt.Println("--dir should be set")
os.Exit(1)
}
if state.Config.Landlock {
lsm.RestrictAdditionalPaths(
landlock.RWFiles(exportDir),
)
if err := lsm.Restrict(); err != nil {
return err
}
}

var err error
allCerts := map[string]DerList{}
Expand Down
27 changes: 27 additions & 0 deletions cmd/sbctl/import-keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"github.com/foxboron/sbctl"
"github.com/foxboron/sbctl/config"
"github.com/foxboron/sbctl/logging"
"github.com/foxboron/sbctl/lsm"
"github.com/landlock-lsm/go-landlock/landlock"
"github.com/spf13/afero"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -93,6 +95,31 @@ func RunImportKeys(cmd *cobra.Command, args []string) error {

state := cmd.Context().Value(stateDataKey{}).(*config.State)

if state.Config.Landlock {
for _, key := range keypairs {
if key.Key != "" {
lsm.RestrictAdditionalPaths(
landlock.RWFiles(key.Key),
)
}
if key.Cert != "" {
lsm.RestrictAdditionalPaths(
landlock.RWFiles(key.Cert),
)
}
}

if importKeysCmdOptions.Directory != "" {
lsm.RestrictAdditionalPaths(
landlock.ROFiles(importKeysCmdOptions.Directory),
)
}

if err := lsm.Restrict(); err != nil {
return err
}
}

if importKeysCmdOptions.Directory != "" {
_, err := state.Fs.Stat(state.Config.Keydir)
if err == nil && !importKeysCmdOptions.Force {
Expand Down
7 changes: 7 additions & 0 deletions cmd/sbctl/list-bundles.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/foxboron/sbctl/config"
"github.com/foxboron/sbctl/hierarchy"
"github.com/foxboron/sbctl/logging"
"github.com/foxboron/sbctl/lsm"
"github.com/spf13/cobra"
)

Expand All @@ -26,6 +27,12 @@ var listBundlesCmd = &cobra.Command{
RunE: func(cmd *cobra.Command, args []string) error {
state := cmd.Context().Value(stateDataKey{}).(*config.State)

if state.Config.Landlock {
if err := lsm.Restrict(); err != nil {
return err
}
}

bundles := []JsonBundle{}
var isSigned bool
err := sbctl.BundleIter(state,
Expand Down
9 changes: 9 additions & 0 deletions cmd/sbctl/list-enrolled-keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"github.com/foxboron/go-uefi/efi"
"github.com/foxboron/go-uefi/efi/signature"
"github.com/foxboron/go-uefi/efi/util"
"github.com/foxboron/sbctl/config"
"github.com/foxboron/sbctl/lsm"
"github.com/spf13/cobra"
)

Expand All @@ -17,6 +19,13 @@ var listKeysCmd = &cobra.Command{
},
Short: "List enrolled keys on the system",
RunE: func(cmd *cobra.Command, args []string) error {
state := cmd.Context().Value(stateDataKey{}).(*config.State)
if state.Config.Landlock {
if err := lsm.Restrict(); err != nil {
return err
}
}

var err error
certList := map[string]([]*x509.Certificate){}

Expand Down
10 changes: 10 additions & 0 deletions cmd/sbctl/list-files.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/foxboron/sbctl/config"
"github.com/foxboron/sbctl/hierarchy"
"github.com/foxboron/sbctl/logging"
"github.com/foxboron/sbctl/lsm"
"github.com/spf13/cobra"
)

Expand All @@ -29,6 +30,15 @@ type JsonFile struct {
func RunList(cmd *cobra.Command, args []string) error {
state := cmd.Context().Value(stateDataKey{}).(*config.State)

if state.Config.Landlock {
if err := sbctl.LandlockFromFileDatabase(state); err != nil {
return err
}
if err := lsm.Restrict(); err != nil {
return err
}
}

files := []JsonFile{}
var isSigned bool
err := sbctl.SigningEntryIter(state,
Expand Down
21 changes: 18 additions & 3 deletions cmd/sbctl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@ import (
"github.com/foxboron/sbctl"
"github.com/foxboron/sbctl/config"
"github.com/foxboron/sbctl/logging"
"github.com/foxboron/sbctl/lsm"
"github.com/spf13/afero"
"github.com/spf13/cobra"
)

type CmdOptions struct {
JsonOutput bool
QuietOutput bool
Config string
JsonOutput bool
QuietOutput bool
Config string
DisableLandlock bool
}

type cliCommand struct {
Expand Down Expand Up @@ -56,6 +58,7 @@ func baseFlags(cmd *cobra.Command) {
flags := cmd.PersistentFlags()
flags.BoolVar(&cmdOptions.JsonOutput, "json", false, "Output as json")
flags.BoolVar(&cmdOptions.QuietOutput, "quiet", false, "Mute info from logging")
flags.BoolVar(&cmdOptions.DisableLandlock, "disable-landlock", false, "disable landlock")
flags.StringVarP(&cmdOptions.Config, "config", "", "", "Path to configuration file")

cmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
Expand Down Expand Up @@ -115,8 +118,20 @@ func main() {
UnsetImmutable().
Open(),
}

// We need to set this after we have parsed stuff
rootCmd.PersistentPreRun = func(_ *cobra.Command, _ []string) {
if cmdOptions.DisableLandlock {
state.Config.Landlock = false
}
}

ctx := context.WithValue(context.Background(), stateDataKey{}, state)

if state.Config.Landlock {
lsm.LandlockRulesFromConfig(state.Config)
}

// This returns i the flag is not found with a specific error
rootCmd.SetFlagErrorFunc(func(cmd *cobra.Command, err error) error {
cmd.Println(err)
Expand Down
7 changes: 7 additions & 0 deletions cmd/sbctl/remove-file.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/foxboron/sbctl"
"github.com/foxboron/sbctl/config"
"github.com/foxboron/sbctl/logging"
"github.com/foxboron/sbctl/lsm"
"github.com/spf13/cobra"
)

Expand All @@ -19,6 +20,12 @@ var removeFileCmd = &cobra.Command{
RunE: func(cmd *cobra.Command, args []string) error {
state := cmd.Context().Value(stateDataKey{}).(*config.State)

if state.Config.Landlock {
if err := lsm.Restrict(); err != nil {
return err
}
}

if len(args) < 1 {
logging.Println("Need to specify file")
os.Exit(1)
Expand Down
6 changes: 6 additions & 0 deletions cmd/sbctl/reset.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/foxboron/sbctl/config"
"github.com/foxboron/sbctl/fs"
"github.com/foxboron/sbctl/logging"
"github.com/foxboron/sbctl/lsm"
"github.com/foxboron/sbctl/stringset"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -157,6 +158,11 @@ func resetDatabase(state *config.State, ev efivar.Efivar, certPaths ...string) e

func RunReset(cmd *cobra.Command, args []string) error {
state := cmd.Context().Value(stateDataKey{}).(*config.State)
if state.Config.Landlock {
if err := lsm.Restrict(); err != nil {
return err
}
}
if err := resetKeys(state); err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/sbctl/rotate-keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func rotateAllKeys(state *config.State, backupDir, newKeysDir string) error {
}

if backupDir == "" {
backupDir = filepath.Join("/var/tmp", fmt.Sprintf("sbctl_backup_keys_%d", time.Now().Unix()))
backupDir = filepath.Join("/var/tmp/sbctl/", fmt.Sprintf("sbctl_backup_keys_%d", time.Now().Unix()))
}

if err := sbctl.CopyDirectory(state.Fs, state.Config.Keydir, backupDir); err != nil {
Expand Down
10 changes: 10 additions & 0 deletions cmd/sbctl/sign-all.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/foxboron/sbctl/config"
"github.com/foxboron/sbctl/hierarchy"
"github.com/foxboron/sbctl/logging"
"github.com/foxboron/sbctl/lsm"
"github.com/spf13/cobra"
)

Expand All @@ -21,6 +22,15 @@ var signAllCmd = &cobra.Command{
Short: "Sign all enrolled files with secure boot keys",
RunE: func(cmd *cobra.Command, args []string) error {
state := cmd.Context().Value(stateDataKey{}).(*config.State)
if state.Config.Landlock {
if err := sbctl.LandlockFromFileDatabase(state); err != nil {
return err
}
if err := lsm.Restrict(); err != nil {
return err
}
}

if generate {
sign = true
if err := generateBundlesCmd.RunE(cmd, args); err != nil {
Expand Down
18 changes: 16 additions & 2 deletions cmd/sbctl/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"github.com/foxboron/sbctl/backend"
"github.com/foxboron/sbctl/config"
"github.com/foxboron/sbctl/logging"
"github.com/foxboron/sbctl/lsm"
"github.com/landlock-lsm/go-landlock/landlock"
"github.com/spf13/cobra"
)

Expand All @@ -21,13 +23,13 @@ var signCmd = &cobra.Command{
Use: "sign",
Short: "Sign a file with secure boot keys",
RunE: func(cmd *cobra.Command, args []string) error {
state := cmd.Context().Value(stateDataKey{}).(*config.State)

if len(args) < 1 {
logging.Print("Requires a file to sign\n")
os.Exit(1)
}

state := cmd.Context().Value(stateDataKey{}).(*config.State)

// Ensure we have absolute paths
file, err := filepath.Abs(args[0])
if err != nil {
Expand All @@ -42,6 +44,18 @@ var signCmd = &cobra.Command{
}
}

if state.Config.Landlock {
lsm.RestrictAdditionalPaths(
// TODO: This doesn't work quite how I want it to
// setting RWFiles to the path gets EACCES
// but setting RWDirs on the dir is fine
landlock.RWDirs(filepath.Dir(output)),
)
if err := lsm.Restrict(); err != nil {
return err
}
}

kh, err := backend.GetKeyHierarchy(state.Fs, state.Config)
if err != nil {
return err
Expand Down
7 changes: 7 additions & 0 deletions cmd/sbctl/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/foxboron/sbctl/certs"
"github.com/foxboron/sbctl/config"
"github.com/foxboron/sbctl/logging"
"github.com/foxboron/sbctl/lsm"
"github.com/foxboron/sbctl/quirks"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -82,6 +83,12 @@ func PrintStatus(s *Status) {
func RunStatus(cmd *cobra.Command, args []string) error {
state := cmd.Context().Value(stateDataKey{}).(*config.State)

if state.Config.Landlock {
if err := lsm.Restrict(); err != nil {
return err
}
}

stat := NewStatus()
if _, err := state.Fs.Stat("/sys/firmware/efi/efivars"); os.IsNotExist(err) {
return fmt.Errorf("system is not booted with UEFI")
Expand Down
Loading

0 comments on commit 009e248

Please sign in to comment.