From 8dc2f858efe348260b88a921952423d555014243 Mon Sep 17 00:00:00 2001 From: Ulrik Strid Date: Wed, 13 Nov 2024 08:22:15 +0100 Subject: [PATCH 01/26] Set CAP_NET_BIND_SERVICE on everything --- nix/tar.go | 37 +++++++++++++++++++++++++++++++++++++ types/types.go | 6 ++++++ 2 files changed, 43 insertions(+) diff --git a/nix/tar.go b/nix/tar.go index 69ffe09..210405f 100644 --- a/nix/tar.go +++ b/nix/tar.go @@ -2,6 +2,8 @@ package nix import ( "archive/tar" + "encoding/binary" + "encoding/hex" "fmt" "io" "os" @@ -124,6 +126,41 @@ func appendFileToTar(tw *tar.Writer, srcPath, dstPath string, info os.FileInfo, } } } + + // Handle capabilities if defined + if len(opts.Capabilities) > 0 { + // Initialize PAXRecords if nil + if hdr.PAXRecords == nil { + hdr.PAXRecords = make(map[string]string) + } + + for _, cap := range opts.Capabilities { + re := regexp.MustCompile(cap.Regex) + if re.Match([]byte(srcPath)) { + // Convert capabilities to binary format + capBytes := make([]byte, 12) // 3 32-bit integers for version 3 + capBytes[0] = 3 // version 3 + + // Process each capability + for _, capStr := range cap.Caps { + switch capStr { + case "CAP_NET_BIND_SERVICE": + // Set bit 10 in all three capability sets (effective, inheritable, permitted) + // Starting from byte 4 (after version and flags) + capBit := uint32(1 << 10) + binary.LittleEndian.PutUint32(capBytes[4:], capBit) + binary.LittleEndian.PutUint32(capBytes[8:], capBit) + binary.LittleEndian.PutUint32(capBytes[12:], capBit) + // Add more cases for other capabilities as needed + } + } + + // Convert to hex string + hexStr := hex.EncodeToString(capBytes) + hdr.PAXRecords["SCHILY.xattr.security.capability"] = hexStr + } + } + } } hdr.ModTime = time.Date(1970, 01, 01, 0, 0, 1, 0, time.UTC) diff --git a/types/types.go b/types/types.go index 0d27cb3..14381f7 100644 --- a/types/types.go +++ b/types/types.go @@ -60,9 +60,15 @@ type PermPath struct { Gname string `json:"gname"` } +type Capability struct { + Regex string `json:"regex"` + Caps []string `json:"caps"` +} + type PathOptions struct { Rewrite Rewrite `json:"rewrite,omitempty"` Perms []Perm `json:"perms,omitempty"` + Capabilities []Capability `json:"capabilities,omitempty"` } type Path struct { From 7577ec8927bd75a0d8395e874dd880860590be8a Mon Sep 17 00:00:00 2001 From: Ulrik Strid Date: Wed, 13 Nov 2024 09:03:00 +0100 Subject: [PATCH 02/26] Thread it through everything --- cmd/layers.go | 23 +++++++++++++++++++++-- cmd/utils.go | 12 ++++++++++++ default.nix | 12 ++++++++++++ nix/layers.go | 23 ++++++++++++++++++----- nix/layers_test.go | 6 +++--- types/types.go | 6 ++++++ 6 files changed, 72 insertions(+), 10 deletions(-) diff --git a/cmd/layers.go b/cmd/layers.go index f204fc6..c857c3b 100644 --- a/cmd/layers.go +++ b/cmd/layers.go @@ -24,6 +24,7 @@ import ( var ignore string var tarDirectory string var permsFilepath string +var capsFilepath string var rewritesFilepath string var historyFilepath string var maxLayers int @@ -57,6 +58,14 @@ var layersReproducibleCmd = &cobra.Command{ os.Exit(1) } } + var caps []types.CapabilityPath + if capsFilepath != "" { + caps, err = readCapsFile(capsFilepath) + if err != nil { + fmt.Fprintf(os.Stderr, "%s", err) + os.Exit(1) + } + } var rewrites []types.RewritePath if rewritesFilepath != "" { rewrites, err = readRewritesFile(rewritesFilepath) @@ -74,7 +83,7 @@ var layersReproducibleCmd = &cobra.Command{ } } - layers, err := nix.NewLayers(storepaths, maxLayers, parents, rewrites, ignore, perms, history) + layers, err := nix.NewLayers(storepaths, maxLayers, parents, rewrites, ignore, perms, caps, history) if err != nil { fmt.Fprintf(os.Stderr, "%s", err) os.Exit(1) @@ -116,6 +125,14 @@ var layersNonReproducibleCmd = &cobra.Command{ os.Exit(1) } } + var caps []types.CapabilityPath + if capsFilepath != "" { + caps, err = readCapsFile(capsFilepath) + if err != nil { + fmt.Fprintf(os.Stderr, "%s", err) + os.Exit(1) + } + } var rewrites []types.RewritePath if rewritesFilepath != "" { rewrites, err = readRewritesFile(rewritesFilepath) @@ -133,7 +150,7 @@ var layersNonReproducibleCmd = &cobra.Command{ } } - layers, err := nix.NewLayersNonReproducible(storepaths, maxLayers, tarDirectory, parents, rewrites, ignore, perms, history) + layers, err := nix.NewLayersNonReproducible(storepaths, maxLayers, tarDirectory, parents, rewrites, ignore, perms, caps, history) if err != nil { fmt.Fprintf(os.Stderr, "%s", err) os.Exit(1) @@ -178,6 +195,7 @@ func init() { layersNonReproducibleCmd.Flags().StringVarP(&rewritesFilepath, "rewrites", "", "", "A JSON file containing a list of path rewrites. Each element of the list is a JSON object with the attributes path, regex and repl: for a given path, the regex is replaced by repl.") layersNonReproducibleCmd.Flags().StringVarP(&permsFilepath, "perms", "", "", "A JSON file containing file permissions") + layersNonReproducibleCmd.Flags().StringVarP(&capsFilepath, "caps", "", "", "A JSON file containing file capabilities") layersNonReproducibleCmd.Flags().StringVarP(&historyFilepath, "history", "", "", "A JSON file containing layer history") layersNonReproducibleCmd.Flags().IntVarP(&maxLayers, "max-layers", "", 1, "The maximum number of layers") @@ -185,6 +203,7 @@ func init() { layersReproducibleCmd.Flags().StringVarP(&ignore, "ignore", "", "", "Ignore the path from the list of storepaths") layersReproducibleCmd.Flags().StringVarP(&rewritesFilepath, "rewrites", "", "", "A JSON file containing path rewrites") layersReproducibleCmd.Flags().StringVarP(&permsFilepath, "perms", "", "", "A JSON file containing file permissions") + layersNonReproducibleCmd.Flags().StringVarP(&capsFilepath, "caps", "", "", "A JSON file containing file capabilities") layersReproducibleCmd.Flags().StringVarP(&historyFilepath, "history", "", "", "A JSON file containing layer history") layersReproducibleCmd.Flags().IntVarP(&maxLayers, "max-layers", "", 1, "The maximum number of layers") diff --git a/cmd/utils.go b/cmd/utils.go index ebb0403..77a72b2 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -21,6 +21,18 @@ func readPermsFile(filename string) (permPaths []types.PermPath, err error) { return } +func readCapsFile(filename string) (capsPaths []types.CapabilityPath, err error) { + content, err := os.ReadFile(filename) + if err != nil { + return capsPaths, err + } + err = json.Unmarshal(content, &capsPaths) + if err != nil { + return capsPaths, err + } + return +} + func readRewritesFile(filename string) (rewritePaths []types.RewritePath, err error) { content, err := os.ReadFile(filename) if err != nil { diff --git a/default.nix b/default.nix index 6ae9cae..a5e2cb5 100644 --- a/default.nix +++ b/default.nix @@ -254,6 +254,15 @@ let # The mode is applied on a specific path. In this path subtree, # the mode is then applied on all files matching the regex. perms ? [], + # A list of capabilities which are set when the tar layer is + # created: these capabilities are not written to the Nix store. + # + # Each element of this permission list is a dict such as + # { path = "a store path"; + # regex = ".*"; + # caps = ["CAP_NET_BIND_SERVICE"]; + # } + capabilities ? [], # The maximun number of layer to create. This is based on the # store path "popularity" as described in # https://grahamc.com/blog/nix-and-layered-docker-images @@ -284,6 +293,8 @@ let rewritesFlag = "--rewrites ${rewritesFile}"; permsFile = pkgs.writeText "perms.json" (l.toJSON perms); permsFlag = l.optionalString (perms != []) "--perms ${permsFile}"; + capsFile = pkgs.writeText "caps.json" (l.toJSON perms); + capsFlag = l.optionalString (capabilities != []) "--caps ${capsFile}"; historyFile = pkgs.writeText "history.json" (l.toJSON metadata); historyFlag = l.optionalString (metadata != {}) "--history ${historyFile}"; allDeps = deps ++ copyToRootList; @@ -296,6 +307,7 @@ let --max-layers ${toString maxLayers} \ ${rewritesFlag} \ ${permsFlag} \ + ${capsFlag} \ ${historyFlag} \ ${tarDirectory} \ ${l.concatMapStringsSep " " (l: l + "/layers.json") layers} \ diff --git a/nix/layers.go b/nix/layers.go index 7001779..86715e0 100644 --- a/nix/layers.go +++ b/nix/layers.go @@ -11,7 +11,7 @@ import ( "github.com/sirupsen/logrus" ) -func getPaths(storePaths []string, parents []types.Layer, rewrites []types.RewritePath, exclude string, permPaths []types.PermPath) types.Paths { +func getPaths(storePaths []string, parents []types.Layer, rewrites []types.RewritePath, exclude string, permPaths []types.PermPath, capPaths []types.CapabilityPath) types.Paths { var paths types.Paths for _, p := range storePaths { path := types.Path{ @@ -36,6 +36,19 @@ func getPaths(storePaths []string, parents []types.Layer, rewrites []types.Rewri if perms != nil { pathOptions.Perms = perms } + var caps []types.Capability + for _, cap := range capPaths { + if p == cap.Path { + hasPathOptions = true + caps = append(caps, types.Capability{ + Regex: cap.Regex, + Caps: cap.Caps, + }) + } + } + if caps != nil { + pathOptions.Capabilities = caps + } for _, rewrite := range rewrites { if p == rewrite.Path { hasPathOptions = true @@ -105,13 +118,13 @@ func newLayers(paths types.Paths, tarDirectory string, maxLayers int, history v1 return layers, nil } -func NewLayers(storePaths []string, maxLayers int, parents []types.Layer, rewrites []types.RewritePath, exclude string, perms []types.PermPath, history v1.History) ([]types.Layer, error) { - paths := getPaths(storePaths, parents, rewrites, exclude, perms) +func NewLayers(storePaths []string, maxLayers int, parents []types.Layer, rewrites []types.RewritePath, exclude string, perms []types.PermPath, caps []types.CapabilityPath, history v1.History) ([]types.Layer, error) { + paths := getPaths(storePaths, parents, rewrites, exclude, perms, caps) return newLayers(paths, "", maxLayers, history) } -func NewLayersNonReproducible(storePaths []string, maxLayers int, tarDirectory string, parents []types.Layer, rewrites []types.RewritePath, exclude string, perms []types.PermPath, history v1.History) (layers []types.Layer, err error) { - paths := getPaths(storePaths, parents, rewrites, exclude, perms) +func NewLayersNonReproducible(storePaths []string, maxLayers int, tarDirectory string, parents []types.Layer, rewrites []types.RewritePath, exclude string, perms []types.PermPath, caps []types.CapabilityPath, history v1.History) (layers []types.Layer, err error) { + paths := getPaths(storePaths, parents, rewrites, exclude, perms, caps) return newLayers(paths, tarDirectory, maxLayers, history) } diff --git a/nix/layers_test.go b/nix/layers_test.go index ef49360..da82421 100644 --- a/nix/layers_test.go +++ b/nix/layers_test.go @@ -20,7 +20,7 @@ func TestPerms(t *testing.T) { Mode: "0641", }, } - layer, err := NewLayers(paths, 1, []types.Layer{}, []types.RewritePath{}, "", perms, v1.History{}) + layer, err := NewLayers(paths, 1, []types.Layer{}, []types.RewritePath{}, "", perms, []types.CapabilityPath{}, v1.History{}) if err != nil { t.Fatalf("%v", err) } @@ -52,7 +52,7 @@ func TestNewLayers(t *testing.T) { paths := []string{ "../data/layer1/file1", } - layer, err := NewLayers(paths, 1, []types.Layer{}, []types.RewritePath{}, "", []types.PermPath{}, v1.History{}) + layer, err := NewLayers(paths, 1, []types.Layer{}, []types.RewritePath{}, "", []types.PermPath{}, []types.CapabilityPath{}, v1.History{}) if err != nil { t.Fatalf("%v", err) } @@ -72,7 +72,7 @@ func TestNewLayers(t *testing.T) { assert.Equal(t, expected, layer) tmpDir := t.TempDir() - layer, err = NewLayersNonReproducible(paths, 1, tmpDir, []types.Layer{}, []types.RewritePath{}, "", []types.PermPath{}, v1.History{}) + layer, err = NewLayersNonReproducible(paths, 1, tmpDir, []types.Layer{}, []types.RewritePath{}, "", []types.PermPath{}, []types.CapabilityPath{}, v1.History{}) if err != nil { t.Fatalf("%v", err) } diff --git a/types/types.go b/types/types.go index 14381f7..465151c 100644 --- a/types/types.go +++ b/types/types.go @@ -65,6 +65,12 @@ type Capability struct { Caps []string `json:"caps"` } +type CapabilityPath struct { + Path string `json:"path"` + Regex string `json:"regex"` + Caps []string `json:"caps"` +} + type PathOptions struct { Rewrite Rewrite `json:"rewrite,omitempty"` Perms []Perm `json:"perms,omitempty"` From 1fd0b526c39851408637652f9a0f0c144a2d612a Mon Sep 17 00:00:00 2001 From: Ulrik Strid Date: Wed, 13 Nov 2024 09:05:42 +0100 Subject: [PATCH 03/26] Add capabilities to buildImage as well --- default.nix | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/default.nix b/default.nix index a5e2cb5..eaefa13 100644 --- a/default.nix +++ b/default.nix @@ -406,6 +406,15 @@ let # The mode is applied on a specific path. In this path subtree, # the mode is then applied on all files matching the regex. perms ? [], + # A list of capabilities which are set when the tar layer is + # created: these capabilities are not written to the Nix store. + # + # Each element of this permission list is a dict such as + # { path = "a store path"; + # regex = ".*"; + # caps = ["CAP_NET_BIND_SERVICE"]; + # } + capabilities ? [], # The maximun number of layer to create. This is based on the # store path "popularity" as described in # https://grahamc.com/blog/nix-and-layered-docker-images From 55af63a9a2345c4e2b44e21aaf594a13063068b2 Mon Sep 17 00:00:00 2001 From: Ulrik Strid Date: Wed, 13 Nov 2024 09:13:03 +0100 Subject: [PATCH 04/26] Fix typo --- cmd/layers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/layers.go b/cmd/layers.go index c857c3b..8e3768b 100644 --- a/cmd/layers.go +++ b/cmd/layers.go @@ -203,7 +203,7 @@ func init() { layersReproducibleCmd.Flags().StringVarP(&ignore, "ignore", "", "", "Ignore the path from the list of storepaths") layersReproducibleCmd.Flags().StringVarP(&rewritesFilepath, "rewrites", "", "", "A JSON file containing path rewrites") layersReproducibleCmd.Flags().StringVarP(&permsFilepath, "perms", "", "", "A JSON file containing file permissions") - layersNonReproducibleCmd.Flags().StringVarP(&capsFilepath, "caps", "", "", "A JSON file containing file capabilities") + layersReproducibleCmd.Flags().StringVarP(&capsFilepath, "caps", "", "", "A JSON file containing file capabilities") layersReproducibleCmd.Flags().StringVarP(&historyFilepath, "history", "", "", "A JSON file containing layer history") layersReproducibleCmd.Flags().IntVarP(&maxLayers, "max-layers", "", 1, "The maximum number of layers") From eafedbdf80da5c0fceabd375a8bac70dfbe5ae54 Mon Sep 17 00:00:00 2001 From: Ulrik Strid Date: Wed, 13 Nov 2024 09:42:03 +0100 Subject: [PATCH 05/26] Better capabiity writing --- nix/tar.go | 62 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/nix/tar.go b/nix/tar.go index 210405f..0e7ab6a 100644 --- a/nix/tar.go +++ b/nix/tar.go @@ -135,30 +135,48 @@ func appendFileToTar(tw *tar.Writer, srcPath, dstPath string, info os.FileInfo, } for _, cap := range opts.Capabilities { - re := regexp.MustCompile(cap.Regex) - if re.Match([]byte(srcPath)) { - // Convert capabilities to binary format - capBytes := make([]byte, 12) // 3 32-bit integers for version 3 - capBytes[0] = 3 // version 3 - - // Process each capability - for _, capStr := range cap.Caps { - switch capStr { - case "CAP_NET_BIND_SERVICE": - // Set bit 10 in all three capability sets (effective, inheritable, permitted) - // Starting from byte 4 (after version and flags) - capBit := uint32(1 << 10) - binary.LittleEndian.PutUint32(capBytes[4:], capBit) - binary.LittleEndian.PutUint32(capBytes[8:], capBit) - binary.LittleEndian.PutUint32(capBytes[12:], capBit) - // Add more cases for other capabilities as needed - } - } + re := regexp.MustCompile(cap.Regex) + if re.Match([]byte(srcPath)) { + // Version 3 capability format + // struct vfs_cap_data { + // __le32 magic_etc; /* magic, version and flags */ + // struct { + // __le32 permitted; /* permitted capabilities */ + // __le32 inheritable; /* inheritable capabilities */ + // } data[1]; + // __le32 effective; /* effective capabilities */ + // }; + capBytes := make([]byte, 20) // 5 32-bit integers + + // Set version 3 and no flags + // magic_etc = 0x20000000 + version + binary.LittleEndian.PutUint32(capBytes[0:], 0x20000003) - // Convert to hex string - hexStr := hex.EncodeToString(capBytes) - hdr.PAXRecords["SCHILY.xattr.security.capability"] = hexStr + var effective, permitted, inheritable uint32 + + // Process each capability + for _, capStr := range cap.Caps { + switch capStr { + case "CAP_NET_BIND_SERVICE": + bit := uint32(1 << 10) // CAP_NET_BIND_SERVICE is 10 + effective |= bit + permitted |= bit + inheritable |= bit + // Add more cases for other capabilities as needed + } } + + // Write permitted and inheritable caps + binary.LittleEndian.PutUint32(capBytes[4:], permitted) + binary.LittleEndian.PutUint32(capBytes[8:], inheritable) + + // Write effective bitmask + binary.LittleEndian.PutUint32(capBytes[12:], effective) + + // Convert to hex string + hexStr := hex.EncodeToString(capBytes) + hdr.PAXRecords["SCHILY.xattr.security.capability"] = hexStr + } } } } From 2826cafc5627171e8852378ea49eaf4297bd28ec Mon Sep 17 00:00:00 2001 From: Ulrik Strid Date: Wed, 13 Nov 2024 14:07:39 +0100 Subject: [PATCH 06/26] Add example --- examples/capabilities.nix | 58 +++++++++++++++++++++++++++++++++++++++ examples/default.nix | 1 + 2 files changed, 59 insertions(+) create mode 100644 examples/capabilities.nix diff --git a/examples/capabilities.nix b/examples/capabilities.nix new file mode 100644 index 0000000..94eb7db --- /dev/null +++ b/examples/capabilities.nix @@ -0,0 +1,58 @@ +{ pkgs, nix2container }: +let + nginxPort = "80"; + nginxConf = pkgs.writeText "nginx.conf" '' + user nobody nobody; + daemon off; + error_log /dev/stdout info; + pid /dev/null; + events {} + http { + access_log /dev/stdout; + server { + listen ${nginxPort}; + index index.html; + location / { + root ${nginxWebRoot}; + } + } + } + ''; + nginxWebRoot = pkgs.writeTextDir "index.html" '' +

Hello from NGINX

+ ''; + nginxVar = pkgs.runCommand "nginx-var" {} '' + mkdir -p $out/var/log/nginx + mkdir -p $out/var/cache/nginx + ''; +in +nix2container.buildImage { + name = "nginx"; + + layers = [ + (nix2container.buildLayer { + copyToRoot = [ + pkgs.dockerTools.fakeNss + nginxVar + ]; + }) + (nix2container.buildLayer { + copyToRoot = [ + pkgs.nginx + ]; + capabilities = [ + { + path = pkgs.nginx; + regex = ""; + caps = [ "CAP_NET_BIND_SERVICE" ]; + } + ]; + }) + ]; + config = { + Cmd = [ "/bin/nginx" "-c" nginxConf ]; + ExposedPorts = { + "${nginxPort}/tcp" = {}; + }; + }; +} diff --git a/examples/default.nix b/examples/default.nix index 9b77596..af958c9 100644 --- a/examples/default.nix +++ b/examples/default.nix @@ -16,4 +16,5 @@ ownership = pkgs.callPackage ./ownership.nix { inherit nix2container; }; created = pkgs.callPackage ./created.nix { inherit nix2container; }; metadata = pkgs.callPackage ./metadata.nix { inherit nix2container; }; + capabilities = pkgs.callPackage ./capabilities.nix { inherit nix2container; }; } From 949a078f354e34b4989bc2dfedd13279b3165582 Mon Sep 17 00:00:00 2001 From: Ulrik Strid Date: Wed, 13 Nov 2024 14:07:53 +0100 Subject: [PATCH 07/26] Set another magic number --- nix/tar.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/tar.go b/nix/tar.go index 0e7ab6a..1c03ca2 100644 --- a/nix/tar.go +++ b/nix/tar.go @@ -150,7 +150,7 @@ func appendFileToTar(tw *tar.Writer, srcPath, dstPath string, info os.FileInfo, // Set version 3 and no flags // magic_etc = 0x20000000 + version - binary.LittleEndian.PutUint32(capBytes[0:], 0x20000003) + binary.LittleEndian.PutUint32(capBytes[0:], 0x03000000) var effective, permitted, inheritable uint32 From f6a90b34920f6dca75c0240e3d8ca921e940965a Mon Sep 17 00:00:00 2001 From: Ulrik Strid Date: Wed, 13 Nov 2024 14:13:02 +0100 Subject: [PATCH 08/26] New approach that works with raw syscall.Setxattr --- nix/tar.go | 66 +++++++++++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/nix/tar.go b/nix/tar.go index 1c03ca2..7f74d52 100644 --- a/nix/tar.go +++ b/nix/tar.go @@ -2,6 +2,7 @@ package nix import ( "archive/tar" + "bytes" "encoding/binary" "encoding/hex" "fmt" @@ -75,6 +76,26 @@ func createDirectory(tw *tar.Writer, path string) error { return nil } +// Version 3 capability format +// struct vfs_cap_data { +// __le32 magic_etc; /* magic, version and flags */ +// struct { +// __le32 permitted; /* permitted capabilities */ +// __le32 inheritable; /* inheritable capabilities */ +// } data[2]; /* realistically, one is enough */ +// __le32 effective; /* effective capabilities */ +// }; +type vfsNsCapData struct { + MagicEtc uint32 + Data [2]struct { + Permitted uint32 + Inheritable uint32 + } + Effective uint32 +} + +const vfsCapRevision3 = 0x03000000 + func appendFileToTar(tw *tar.Writer, srcPath, dstPath string, info os.FileInfo, opts *types.PathOptions) error { var link string var err error @@ -137,41 +158,20 @@ func appendFileToTar(tw *tar.Writer, srcPath, dstPath string, info os.FileInfo, for _, cap := range opts.Capabilities { re := regexp.MustCompile(cap.Regex) if re.Match([]byte(srcPath)) { - // Version 3 capability format - // struct vfs_cap_data { - // __le32 magic_etc; /* magic, version and flags */ - // struct { - // __le32 permitted; /* permitted capabilities */ - // __le32 inheritable; /* inheritable capabilities */ - // } data[1]; - // __le32 effective; /* effective capabilities */ - // }; - capBytes := make([]byte, 20) // 5 32-bit integers - - // Set version 3 and no flags - // magic_etc = 0x20000000 + version - binary.LittleEndian.PutUint32(capBytes[0:], 0x03000000) - - var effective, permitted, inheritable uint32 - - // Process each capability - for _, capStr := range cap.Caps { - switch capStr { - case "CAP_NET_BIND_SERVICE": - bit := uint32(1 << 10) // CAP_NET_BIND_SERVICE is 10 - effective |= bit - permitted |= bit - inheritable |= bit - // Add more cases for other capabilities as needed - } + data := vfsNsCapData{MagicEtc: vfsCapRevision3 | uint32(0)} + + data.Data[0].Permitted = uint32(10) + data.Data[0].Inheritable = uint32(10) + data.Data[1].Permitted = uint32(10 >> 32) + data.Data[1].Inheritable = uint32(10 >> 32) + data.Effective = uint32(10) + + buf := &bytes.Buffer{} + if err := binary.Write(buf, binary.LittleEndian, data); err != nil { + return err } - // Write permitted and inheritable caps - binary.LittleEndian.PutUint32(capBytes[4:], permitted) - binary.LittleEndian.PutUint32(capBytes[8:], inheritable) - - // Write effective bitmask - binary.LittleEndian.PutUint32(capBytes[12:], effective) + capBytes := buf.Bytes() // Convert to hex string hexStr := hex.EncodeToString(capBytes) From 4be0ec45bbbbb6dddc83d9915c9bf51f1025ac69 Mon Sep 17 00:00:00 2001 From: Ulrik Strid Date: Wed, 13 Nov 2024 14:16:22 +0100 Subject: [PATCH 09/26] Add some logging --- nix/tar.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nix/tar.go b/nix/tar.go index 7f74d52..178afce 100644 --- a/nix/tar.go +++ b/nix/tar.go @@ -173,8 +173,10 @@ func appendFileToTar(tw *tar.Writer, srcPath, dstPath string, info os.FileInfo, capBytes := buf.Bytes() + // Convert to hex string hexStr := hex.EncodeToString(capBytes) + fmt.Printf("capBytes: %v - %s\n", capBytes, hexStr) hdr.PAXRecords["SCHILY.xattr.security.capability"] = hexStr } } From 8a05731019ec809d8458adef43ee36dcdd9d0232 Mon Sep 17 00:00:00 2001 From: Ulrik Strid Date: Wed, 13 Nov 2024 14:19:47 +0100 Subject: [PATCH 10/26] Don't care about regex --- nix/tar.go | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/nix/tar.go b/nix/tar.go index 178afce..fc58c8d 100644 --- a/nix/tar.go +++ b/nix/tar.go @@ -155,31 +155,26 @@ func appendFileToTar(tw *tar.Writer, srcPath, dstPath string, info os.FileInfo, hdr.PAXRecords = make(map[string]string) } - for _, cap := range opts.Capabilities { - re := regexp.MustCompile(cap.Regex) - if re.Match([]byte(srcPath)) { - data := vfsNsCapData{MagicEtc: vfsCapRevision3 | uint32(0)} - - data.Data[0].Permitted = uint32(10) - data.Data[0].Inheritable = uint32(10) - data.Data[1].Permitted = uint32(10 >> 32) - data.Data[1].Inheritable = uint32(10 >> 32) - data.Effective = uint32(10) - - buf := &bytes.Buffer{} - if err := binary.Write(buf, binary.LittleEndian, data); err != nil { - return err - } + data := vfsNsCapData{MagicEtc: vfsCapRevision3 | uint32(0)} - capBytes := buf.Bytes() + data.Data[0].Permitted = uint32(10) + data.Data[0].Inheritable = uint32(10) + data.Data[1].Permitted = uint32(10 >> 32) + data.Data[1].Inheritable = uint32(10 >> 32) + data.Effective = uint32(10) - - // Convert to hex string - hexStr := hex.EncodeToString(capBytes) - fmt.Printf("capBytes: %v - %s\n", capBytes, hexStr) - hdr.PAXRecords["SCHILY.xattr.security.capability"] = hexStr - } + buf := &bytes.Buffer{} + if err := binary.Write(buf, binary.LittleEndian, data); err != nil { + return err } + + capBytes := buf.Bytes() + + + // Convert to hex string + hexStr := hex.EncodeToString(capBytes) + fmt.Printf("capBytes: %v - %s\n", capBytes, hexStr) + hdr.PAXRecords["SCHILY.xattr.security.capability"] = hexStr } } From 3a9356c52b259d36f3ee7671b044add331014503 Mon Sep 17 00:00:00 2001 From: Ulrik Strid Date: Wed, 13 Nov 2024 14:51:24 +0100 Subject: [PATCH 11/26] Update --- nix/tar.go | 48 ++++++++++++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/nix/tar.go b/nix/tar.go index fc58c8d..0182106 100644 --- a/nix/tar.go +++ b/nix/tar.go @@ -150,31 +150,43 @@ func appendFileToTar(tw *tar.Writer, srcPath, dstPath string, info os.FileInfo, // Handle capabilities if defined if len(opts.Capabilities) > 0 { - // Initialize PAXRecords if nil + // Initialize PAXRecords if nil if hdr.PAXRecords == nil { hdr.PAXRecords = make(map[string]string) } - data := vfsNsCapData{MagicEtc: vfsCapRevision3 | uint32(0)} - - data.Data[0].Permitted = uint32(10) - data.Data[0].Inheritable = uint32(10) - data.Data[1].Permitted = uint32(10 >> 32) - data.Data[1].Inheritable = uint32(10 >> 32) - data.Effective = uint32(10) + for _, cap := range opts.Capabilities { + re := regexp.MustCompile(cap.Regex) + if re.Match([]byte(hdr.Name)) { + + data := vfsNsCapData{MagicEtc: vfsCapRevision3 | uint32(0)} + + var permitted, inheritable, effective uint32 + for _, capStr := range cap.Caps { + switch capStr { + case "CAP_NET_BIND_SERVICE": + bit := uint32(1 << 10) // CAP_NET_BIND_SERVICE is 10 + permitted |= bit + inheritable |= bit + effective |= bit + // Add more cases for other capabilities as needed + } + } - buf := &bytes.Buffer{} - if err := binary.Write(buf, binary.LittleEndian, data); err != nil { - return err - } + data.Data[0].Permitted = permitted + data.Data[0].Inheritable = inheritable + data.Effective = effective - capBytes := buf.Bytes() + buf := &bytes.Buffer{} + if err := binary.Write(buf, binary.LittleEndian, data); err != nil { + fmt.Printf("Failed to write buffer: %v\n", err) + continue + } - - // Convert to hex string - hexStr := hex.EncodeToString(capBytes) - fmt.Printf("capBytes: %v - %s\n", capBytes, hexStr) - hdr.PAXRecords["SCHILY.xattr.security.capability"] = hexStr + capBytes := buf.Bytes() + hdr.PAXRecords["SCHILY.xattr.security.capability"] = hex.EncodeToString(capBytes) + } + } } } From 5a5fe38cadf9aaa4803ab8613a2561db3aa02a8c Mon Sep 17 00:00:00 2001 From: Ulrik Strid Date: Wed, 13 Nov 2024 14:55:48 +0100 Subject: [PATCH 12/26] Set both sets --- nix/tar.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nix/tar.go b/nix/tar.go index 0182106..2ff7b0c 100644 --- a/nix/tar.go +++ b/nix/tar.go @@ -165,7 +165,7 @@ func appendFileToTar(tw *tar.Writer, srcPath, dstPath string, info os.FileInfo, for _, capStr := range cap.Caps { switch capStr { case "CAP_NET_BIND_SERVICE": - bit := uint32(1 << 10) // CAP_NET_BIND_SERVICE is 10 + bit := uint32(10) // CAP_NET_BIND_SERVICE is 10 permitted |= bit inheritable |= bit effective |= bit @@ -175,6 +175,8 @@ func appendFileToTar(tw *tar.Writer, srcPath, dstPath string, info os.FileInfo, data.Data[0].Permitted = permitted data.Data[0].Inheritable = inheritable + data.Data[1].Permitted = uint32(10 >> 32) + data.Data[1].Inheritable = uint32(10 >> 32) data.Effective = effective buf := &bytes.Buffer{} From 0bce07d281246c4bd690bb181a68a4153ecbccd0 Mon Sep 17 00:00:00 2001 From: Ulrik Strid Date: Wed, 13 Nov 2024 14:59:23 +0100 Subject: [PATCH 13/26] Add some regex logs and fix bug --- nix/tar.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nix/tar.go b/nix/tar.go index 2ff7b0c..3c9cab2 100644 --- a/nix/tar.go +++ b/nix/tar.go @@ -156,8 +156,10 @@ func appendFileToTar(tw *tar.Writer, srcPath, dstPath string, info os.FileInfo, } for _, cap := range opts.Capabilities { + fmt.Printf("path regex: %s\n", cap.Regex) re := regexp.MustCompile(cap.Regex) - if re.Match([]byte(hdr.Name)) { + if re.Match([]byte(srcPath)) { + fmt.Printf("Regex matches!: %s\n", srcPath) data := vfsNsCapData{MagicEtc: vfsCapRevision3 | uint32(0)} From 061ce07a10c4ba1000f5fdf142c5da2b1d5b9d5f Mon Sep 17 00:00:00 2001 From: Ulrik Strid Date: Wed, 13 Nov 2024 15:02:19 +0100 Subject: [PATCH 14/26] Logs? --- nix/tar.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nix/tar.go b/nix/tar.go index 3c9cab2..1467184 100644 --- a/nix/tar.go +++ b/nix/tar.go @@ -156,10 +156,10 @@ func appendFileToTar(tw *tar.Writer, srcPath, dstPath string, info os.FileInfo, } for _, cap := range opts.Capabilities { - fmt.Printf("path regex: %s\n", cap.Regex) + logrus.Infof("path regex: %s", cap.Regex) re := regexp.MustCompile(cap.Regex) if re.Match([]byte(srcPath)) { - fmt.Printf("Regex matches!: %s\n", srcPath) + logrus.Infof("Regex matches!: %s", srcPath) data := vfsNsCapData{MagicEtc: vfsCapRevision3 | uint32(0)} From 81a1d4dd41c742fccdb0d34c5023604ab51afe35 Mon Sep 17 00:00:00 2001 From: Ulrik Strid Date: Wed, 13 Nov 2024 15:03:59 +0100 Subject: [PATCH 15/26] More logs? --- nix/tar.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nix/tar.go b/nix/tar.go index 1467184..042df98 100644 --- a/nix/tar.go +++ b/nix/tar.go @@ -148,8 +148,12 @@ func appendFileToTar(tw *tar.Writer, srcPath, dstPath string, info os.FileInfo, } } + + logrus.Infof("Check for capabilities") + // Handle capabilities if defined if len(opts.Capabilities) > 0 { + logrus.Infof("We have capabilities") // Initialize PAXRecords if nil if hdr.PAXRecords == nil { hdr.PAXRecords = make(map[string]string) From f299825d5700ca9564151301f4c0e2b5502bc43c Mon Sep 17 00:00:00 2001 From: Ulrik Strid Date: Wed, 13 Nov 2024 15:10:50 +0100 Subject: [PATCH 16/26] Fix capsFile --- default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/default.nix b/default.nix index eaefa13..e65694d 100644 --- a/default.nix +++ b/default.nix @@ -293,7 +293,7 @@ let rewritesFlag = "--rewrites ${rewritesFile}"; permsFile = pkgs.writeText "perms.json" (l.toJSON perms); permsFlag = l.optionalString (perms != []) "--perms ${permsFile}"; - capsFile = pkgs.writeText "caps.json" (l.toJSON perms); + capsFile = pkgs.writeText "caps.json" (l.toJSON capabilities); capsFlag = l.optionalString (capabilities != []) "--caps ${capsFile}"; historyFile = pkgs.writeText "history.json" (l.toJSON metadata); historyFlag = l.optionalString (metadata != {}) "--history ${historyFile}"; From 077bed6972bbabe3e2b940cd3f980533f5f9f388 Mon Sep 17 00:00:00 2001 From: Ulrik Strid Date: Wed, 13 Nov 2024 15:28:28 +0100 Subject: [PATCH 17/26] Skip hex --- nix/tar.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/nix/tar.go b/nix/tar.go index 042df98..c3e7380 100644 --- a/nix/tar.go +++ b/nix/tar.go @@ -4,7 +4,6 @@ import ( "archive/tar" "bytes" "encoding/binary" - "encoding/hex" "fmt" "io" "os" @@ -154,7 +153,7 @@ func appendFileToTar(tw *tar.Writer, srcPath, dstPath string, info os.FileInfo, // Handle capabilities if defined if len(opts.Capabilities) > 0 { logrus.Infof("We have capabilities") - // Initialize PAXRecords if nil + // Initialize PAXRecords if nil if hdr.PAXRecords == nil { hdr.PAXRecords = make(map[string]string) } @@ -192,7 +191,7 @@ func appendFileToTar(tw *tar.Writer, srcPath, dstPath string, info os.FileInfo, } capBytes := buf.Bytes() - hdr.PAXRecords["SCHILY.xattr.security.capability"] = hex.EncodeToString(capBytes) + hdr.PAXRecords["SCHILY.xattr.security.capability"] = string(capBytes) } } } From 2224f65b5db796d4b30d8c84823813886124020c Mon Sep 17 00:00:00 2001 From: Ulrik Strid Date: Wed, 13 Nov 2024 15:48:32 +0100 Subject: [PATCH 18/26] Tune logging --- nix/tar.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/nix/tar.go b/nix/tar.go index c3e7380..406e313 100644 --- a/nix/tar.go +++ b/nix/tar.go @@ -148,21 +148,18 @@ func appendFileToTar(tw *tar.Writer, srcPath, dstPath string, info os.FileInfo, } - logrus.Infof("Check for capabilities") - // Handle capabilities if defined if len(opts.Capabilities) > 0 { - logrus.Infof("We have capabilities") // Initialize PAXRecords if nil if hdr.PAXRecords == nil { hdr.PAXRecords = make(map[string]string) } for _, cap := range opts.Capabilities { - logrus.Infof("path regex: %s", cap.Regex) + logrus.Infof("path regex: %s path: %s", cap.Regex, srcPath) re := regexp.MustCompile(cap.Regex) if re.Match([]byte(srcPath)) { - logrus.Infof("Regex matches!: %s", srcPath) + logrus.Infof("Regex matches!: %s path: %s", cap.Regex, srcPath) data := vfsNsCapData{MagicEtc: vfsCapRevision3 | uint32(0)} From ed28f53877c81c5fd84c9bec42bbeda25e2c66b9 Mon Sep 17 00:00:00 2001 From: Ulrik Strid Date: Wed, 13 Nov 2024 16:02:47 +0100 Subject: [PATCH 19/26] Add dstPath to log --- nix/tar.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/tar.go b/nix/tar.go index 406e313..ac828b6 100644 --- a/nix/tar.go +++ b/nix/tar.go @@ -156,7 +156,7 @@ func appendFileToTar(tw *tar.Writer, srcPath, dstPath string, info os.FileInfo, } for _, cap := range opts.Capabilities { - logrus.Infof("path regex: %s path: %s", cap.Regex, srcPath) + logrus.Infof("path regex: %s path: %s dst: %s", cap.Regex, srcPath, dstPath) re := regexp.MustCompile(cap.Regex) if re.Match([]byte(srcPath)) { logrus.Infof("Regex matches!: %s path: %s", cap.Regex, srcPath) From 8c81a5013a9e2117bde130746b0c8c9c6f1a3b57 Mon Sep 17 00:00:00 2001 From: Ulrik Strid Date: Wed, 13 Nov 2024 16:19:44 +0100 Subject: [PATCH 20/26] hex encode again --- nix/tar.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nix/tar.go b/nix/tar.go index ac828b6..2fa5b79 100644 --- a/nix/tar.go +++ b/nix/tar.go @@ -4,6 +4,7 @@ import ( "archive/tar" "bytes" "encoding/binary" + "encoding/hex" "fmt" "io" "os" @@ -187,8 +188,10 @@ func appendFileToTar(tw *tar.Writer, srcPath, dstPath string, info os.FileInfo, continue } + buf.Bytes() + capBytes := buf.Bytes() - hdr.PAXRecords["SCHILY.xattr.security.capability"] = string(capBytes) + hdr.PAXRecords["SCHILY.xattr.security.capability"] = hex.EncodeToString(capBytes) } } } From ab7c1ebf9beeb9d4ccde17980c98b34030f26cd4 Mon Sep 17 00:00:00 2001 From: Ulrik Strid Date: Wed, 13 Nov 2024 16:58:07 +0100 Subject: [PATCH 21/26] Only add capability to binary in example --- examples/capabilities.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/capabilities.nix b/examples/capabilities.nix index 94eb7db..83ec908 100644 --- a/examples/capabilities.nix +++ b/examples/capabilities.nix @@ -43,7 +43,7 @@ nix2container.buildImage { capabilities = [ { path = pkgs.nginx; - regex = ""; + regex = "bin/nginx"; caps = [ "CAP_NET_BIND_SERVICE" ]; } ]; From b559d1885235256713f33053b18d4de47d2479ba Mon Sep 17 00:00:00 2001 From: Ulrik Strid Date: Wed, 13 Nov 2024 16:59:58 +0100 Subject: [PATCH 22/26] Commit before day ends --- nix/tar.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/nix/tar.go b/nix/tar.go index 2fa5b79..fff7393 100644 --- a/nix/tar.go +++ b/nix/tar.go @@ -157,7 +157,6 @@ func appendFileToTar(tw *tar.Writer, srcPath, dstPath string, info os.FileInfo, } for _, cap := range opts.Capabilities { - logrus.Infof("path regex: %s path: %s dst: %s", cap.Regex, srcPath, dstPath) re := regexp.MustCompile(cap.Regex) if re.Match([]byte(srcPath)) { logrus.Infof("Regex matches!: %s path: %s", cap.Regex, srcPath) @@ -182,16 +181,19 @@ func appendFileToTar(tw *tar.Writer, srcPath, dstPath string, info os.FileInfo, data.Data[1].Inheritable = uint32(10 >> 32) data.Effective = effective + + logrus.Infof("Regex matches!: %v", data) + buf := &bytes.Buffer{} if err := binary.Write(buf, binary.LittleEndian, data); err != nil { fmt.Printf("Failed to write buffer: %v\n", err) continue } - buf.Bytes() - capBytes := buf.Bytes() - hdr.PAXRecords["SCHILY.xattr.security.capability"] = hex.EncodeToString(capBytes) + logrus.Infof("cap bytes: %v hex: %s", capBytes, hex.EncodeToString(capBytes)) + + hdr.PAXRecords["SCHILY.xattr.security.capability"] = string(capBytes) } } } From 248d1e8b6080e6bd1dd8201dde95c65c6a5c4a81 Mon Sep 17 00:00:00 2001 From: Ulrik Strid Date: Thu, 14 Nov 2024 13:07:53 +0100 Subject: [PATCH 23/26] Try setting bit another way --- nix/tar.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/nix/tar.go b/nix/tar.go index fff7393..30772bf 100644 --- a/nix/tar.go +++ b/nix/tar.go @@ -167,7 +167,7 @@ func appendFileToTar(tw *tar.Writer, srcPath, dstPath string, info os.FileInfo, for _, capStr := range cap.Caps { switch capStr { case "CAP_NET_BIND_SERVICE": - bit := uint32(10) // CAP_NET_BIND_SERVICE is 10 + bit := uint32(1 << 10) // CAP_NET_BIND_SERVICE is 10 permitted |= bit inheritable |= bit effective |= bit @@ -181,8 +181,7 @@ func appendFileToTar(tw *tar.Writer, srcPath, dstPath string, info os.FileInfo, data.Data[1].Inheritable = uint32(10 >> 32) data.Effective = effective - - logrus.Infof("Regex matches!: %v", data) + logrus.Infof("capabilities data: %v", data) buf := &bytes.Buffer{} if err := binary.Write(buf, binary.LittleEndian, data); err != nil { From f674e25c1b2b1e43c59d4209ee076fe474e31171 Mon Sep 17 00:00:00 2001 From: Ulrik Strid Date: Thu, 14 Nov 2024 13:13:14 +0100 Subject: [PATCH 24/26] Don't encode capability bytes --- nix/tar.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nix/tar.go b/nix/tar.go index 30772bf..71eea5d 100644 --- a/nix/tar.go +++ b/nix/tar.go @@ -4,7 +4,6 @@ import ( "archive/tar" "bytes" "encoding/binary" - "encoding/hex" "fmt" "io" "os" @@ -190,7 +189,7 @@ func appendFileToTar(tw *tar.Writer, srcPath, dstPath string, info os.FileInfo, } capBytes := buf.Bytes() - logrus.Infof("cap bytes: %v hex: %s", capBytes, hex.EncodeToString(capBytes)) + logrus.Infof("cap bytes: %v hex: %s", capBytes, string(capBytes)) hdr.PAXRecords["SCHILY.xattr.security.capability"] = string(capBytes) } From 9112cf67edae2fdbf405487d387d20c11b111ae7 Mon Sep 17 00:00:00 2001 From: Ulrik Strid Date: Thu, 14 Nov 2024 13:18:35 +0100 Subject: [PATCH 25/26] Break out to a more robust solution --- nix/tar.go | 30 ++-------------- nix/vfs_cap.go | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 28 deletions(-) create mode 100644 nix/vfs_cap.go diff --git a/nix/tar.go b/nix/tar.go index 71eea5d..fdd15a4 100644 --- a/nix/tar.go +++ b/nix/tar.go @@ -2,8 +2,6 @@ package nix import ( "archive/tar" - "bytes" - "encoding/binary" "fmt" "io" "os" @@ -160,35 +158,11 @@ func appendFileToTar(tw *tar.Writer, srcPath, dstPath string, info os.FileInfo, if re.Match([]byte(srcPath)) { logrus.Infof("Regex matches!: %s path: %s", cap.Regex, srcPath) - data := vfsNsCapData{MagicEtc: vfsCapRevision3 | uint32(0)} - - var permitted, inheritable, effective uint32 - for _, capStr := range cap.Caps { - switch capStr { - case "CAP_NET_BIND_SERVICE": - bit := uint32(1 << 10) // CAP_NET_BIND_SERVICE is 10 - permitted |= bit - inheritable |= bit - effective |= bit - // Add more cases for other capabilities as needed - } - } - - data.Data[0].Permitted = permitted - data.Data[0].Inheritable = inheritable - data.Data[1].Permitted = uint32(10 >> 32) - data.Data[1].Inheritable = uint32(10 >> 32) - data.Effective = effective + data := NewVFSCapData(uint32(1 << CAP_NET_BIND_SERVICE), uint32(1 << CAP_NET_BIND_SERVICE), true, 0) logrus.Infof("capabilities data: %v", data) - buf := &bytes.Buffer{} - if err := binary.Write(buf, binary.LittleEndian, data); err != nil { - fmt.Printf("Failed to write buffer: %v\n", err) - continue - } - - capBytes := buf.Bytes() + capBytes := data.ToBytes() logrus.Infof("cap bytes: %v hex: %s", capBytes, string(capBytes)) hdr.PAXRecords["SCHILY.xattr.security.capability"] = string(capBytes) diff --git a/nix/vfs_cap.go b/nix/vfs_cap.go new file mode 100644 index 0000000..b9d41e2 --- /dev/null +++ b/nix/vfs_cap.go @@ -0,0 +1,98 @@ +package nix + +import ( + "encoding/binary" + "fmt" +) + +const ( + // VFS_CAP_REVISION_3 is version 3 of the capability implementation + VFS_CAP_REVISION_3 = 0x03000000 + + // VFS_CAP_U32 defines the number of 32-bit words per capability set + VFS_CAP_U32 = 2 + + // VFS_CAP_FLAGS_EFFECTIVE is the flag indicating if the effective bit is set + VFS_CAP_FLAGS_EFFECTIVE = 0x000001 + + // Capability constants + CAP_NET_BIND_SERVICE = 10 +) + +// CapData represents the capability data pair of permitted and inheritable flags +type CapData struct { + Permitted uint32 // Little endian + Inheritable uint32 // Little endian +} + +// VFSCapData represents the full capability data structure as stored in the filesystem +type VFSCapData struct { + MagicEtc uint32 // Little endian + Data [VFS_CAP_U32]CapData + RootID uint32 // Little endian (new in revision 3) +} + +// NewVFSCapData creates a new VFSCapData structure with the given capabilities +func NewVFSCapData(permitted, inheritable uint32, effective bool, rootid uint32) *VFSCapData { + magic := VFS_CAP_REVISION_3 + if effective { + magic |= VFS_CAP_FLAGS_EFFECTIVE + } + + return &VFSCapData{ + MagicEtc: uint32(magic), + Data: [VFS_CAP_U32]CapData{ + { + Permitted: permitted, + Inheritable: inheritable, + }, + { + Permitted: 0, + Inheritable: 0, + }, + }, + RootID: rootid, + } +} + +// ToBytes converts the VFSCapData structure to a byte slice in little endian format +func (vfs *VFSCapData) ToBytes() []byte { + // Size is now: 4 (magic) + (4 + 4) * 2 (two CapData structs) + 4 (rootid) = 24 bytes + b := make([]byte, 24) + + // Write magic_etc + binary.LittleEndian.PutUint32(b[0:], vfs.MagicEtc) + + // Write first CapData + binary.LittleEndian.PutUint32(b[4:], vfs.Data[0].Permitted) + binary.LittleEndian.PutUint32(b[8:], vfs.Data[0].Inheritable) + + // Write second CapData + binary.LittleEndian.PutUint32(b[12:], vfs.Data[1].Permitted) + binary.LittleEndian.PutUint32(b[16:], vfs.Data[1].Inheritable) + + // Write rootid + binary.LittleEndian.PutUint32(b[20:], vfs.RootID) + + return b +} + +// FromBytes parses a byte slice into a VFSCapData structure +func FromBytes(b []byte) (*VFSCapData, error) { + if len(b) < 12 { // 4 + (4 + 4) * VFS_CAP_U32 + return nil, fmt.Errorf("byte slice too short: got %d bytes, want at least 12", len(b)) + } + + vfs := &VFSCapData{} + + // Read magic_etc + vfs.MagicEtc = binary.LittleEndian.Uint32(b[0:]) + + // Read permitted + vfs.Data[0].Permitted = binary.LittleEndian.Uint32(b[4:]) + + // Read inheritable + vfs.Data[0].Inheritable = binary.LittleEndian.Uint32(b[8:]) + + return vfs, nil +} \ No newline at end of file From 5e6b17b2ea175e40bab5e86e7f9223d8106afcfa Mon Sep 17 00:00:00 2001 From: Ulrik Strid Date: Thu, 14 Nov 2024 15:32:50 +0100 Subject: [PATCH 26/26] Add capabilities to buildImage correctly --- default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/default.nix b/default.nix index e65694d..0625203 100644 --- a/default.nix +++ b/default.nix @@ -471,7 +471,7 @@ let ]; customizationLayer = buildLayer { - inherit maxLayers; + inherit maxLayers capabilities; perms = perms'; copyToRoot = if initializeNixDatabase then copyToRootList ++ [nixDatabase]