aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Biggers <ebiggers@google.com>2020-03-17 21:10:58 -0700
committerEric Biggers <ebiggers@google.com>2020-03-23 13:20:27 -0700
commitec85cc8f987647c2b264c1f95dadda0f71c3d991 (patch)
tree5695c9a84004ec40a9cb3d774c6bb2aa9503e605
parentae886a89f541a74255c9a41f7fa504a82ee6413e (diff)
Create /etc/fscrypt.conf with policy_version 2 on kernel v5.4+
v2 encryption policies are now recommended, due to various security and usability advantages over v1 policies. Many people have been running into the usability problems with v1, so it's desirable to get people onto v2 without having to manually opt-in. Therefore, when 'fscrypt setup' creates /etc/fscrypt.conf, enable policy_version 2 automatically if the kernel supports it. I decided to go with this solution over the policy_version "auto" I suggested originally because this way is simpler, it can still be changed to "auto" later if desired, and "auto" might require changing how we parse the config file (since currently the config file is mapped directly to a protobuf where policy_version is an 'int' and is shared with EncryptionOptions). Resolves https://github.com/google/fscrypt/issues/182
-rw-r--r--actions/config.go9
-rw-r--r--actions/config_test.go26
-rw-r--r--actions/context_test.go2
-rw-r--r--cmd/fscrypt/setup.go16
-rw-r--r--util/util.go23
-rw-r--r--util/util_test.go7
6 files changed, 78 insertions, 5 deletions
diff --git a/actions/config.go b/actions/config.go
index 3433438..2463b95 100644
--- a/actions/config.go
+++ b/actions/config.go
@@ -58,8 +58,9 @@ var (
// CreateConfigFile creates a new config file at the appropriate location with
// the appropriate hashing costs and encryption parameters. The hashing will be
-// configured to take as long as the specified time target.
-func CreateConfigFile(target time.Duration) error {
+// configured to take as long as the specified time target. In addition, the
+// version of encryption policy to use may be overridden from the default of v1.
+func CreateConfigFile(target time.Duration, policyVersion int64) error {
// Create the config file before computing the hashing costs, so we fail
// immediately if the program has insufficient permissions.
configFile, err := filesystem.OpenFileOverridingUmask(ConfigFileLocation,
@@ -77,6 +78,10 @@ func CreateConfigFile(target time.Duration) error {
Options: metadata.DefaultOptions,
}
+ if policyVersion != 0 {
+ config.Options.PolicyVersion = policyVersion
+ }
+
if config.HashCosts, err = getHashingCosts(target); err != nil {
return err
}
diff --git a/actions/config_test.go b/actions/config_test.go
index 02c89e6..3599667 100644
--- a/actions/config_test.go
+++ b/actions/config_test.go
@@ -26,6 +26,8 @@ import (
"time"
"golang.org/x/sys/unix"
+
+ "github.com/google/fscrypt/metadata"
)
// Test that the global config file is created with mode 0644, regardless of the
@@ -42,7 +44,7 @@ func TestConfigFileIsCreatedWithCorrectMode(t *testing.T) {
defer os.RemoveAll(tempDir)
ConfigFileLocation = filepath.Join(tempDir, "test.conf")
- if err = CreateConfigFile(time.Millisecond); err != nil {
+ if err = CreateConfigFile(time.Millisecond, 0); err != nil {
t.Fatal(err)
}
fileInfo, err := os.Stat(ConfigFileLocation)
@@ -53,3 +55,25 @@ func TestConfigFileIsCreatedWithCorrectMode(t *testing.T) {
t.Error("Expected newly created config file to have mode 0644")
}
}
+
+func TestCreateConfigFileV2Policy(t *testing.T) {
+ tempDir, err := ioutil.TempDir("", "fscrypt")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tempDir)
+ ConfigFileLocation = filepath.Join(tempDir, "test.conf")
+
+ if err = CreateConfigFile(time.Millisecond, 2); err != nil {
+ t.Fatal(err)
+ }
+
+ var config *metadata.Config
+ config, err = getConfig()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if config.Options.PolicyVersion != 2 {
+ t.Error("Expected PolicyVersion 2")
+ }
+}
diff --git a/actions/context_test.go b/actions/context_test.go
index 4f93776..4488a6b 100644
--- a/actions/context_test.go
+++ b/actions/context_test.go
@@ -52,7 +52,7 @@ func setupContext() (ctx *Context, err error) {
return nil, fmt.Errorf("created context at %q without config file", badCtx.Mount.Path)
}
- if err = CreateConfigFile(testTime); err != nil {
+ if err = CreateConfigFile(testTime, 0); err != nil {
return nil, err
}
defer func() {
diff --git a/cmd/fscrypt/setup.go b/cmd/fscrypt/setup.go
index 328788a..7b9bebb 100644
--- a/cmd/fscrypt/setup.go
+++ b/cmd/fscrypt/setup.go
@@ -50,8 +50,22 @@ func createGlobalConfig(w io.Writer, path string) error {
return err
}
+ // v2 encryption policies are recommended, so set policy_version 2 when
+ // the kernel supports it. v2 policies are supported by upstream Linux
+ // v5.4 and later. For now we simply check the kernel version. Ideally
+ // we'd instead check whether setting a v2 policy actually works, in
+ // order to also detect backports of the kernel patches. However, that's
+ // hard because from this context (creating /etc/fscrypt.conf) we may
+ // not yet have access to a filesystem that supports encryption.
+ var policyVersion int64
+ if util.IsKernelVersionAtLeast(5, 4) {
+ fmt.Fprintln(w, "Defaulting to policy_version 2 because kernel supports it.")
+ policyVersion = 2
+ } else {
+ fmt.Fprintln(w, "Defaulting to policy_version 1 because kernel doesn't support v2.")
+ }
fmt.Fprintln(w, "Customizing passphrase hashing difficulty for this system...")
- err = actions.CreateConfigFile(timeTargetFlag.Value)
+ err = actions.CreateConfigFile(timeTargetFlag.Value, policyVersion)
if err != nil {
return err
}
diff --git a/util/util.go b/util/util.go
index 97ee33c..d97a7ae 100644
--- a/util/util.go
+++ b/util/util.go
@@ -25,10 +25,14 @@ package util
import (
"bufio"
+ "fmt"
+ "log"
"os"
"os/user"
"strconv"
"unsafe"
+
+ "golang.org/x/sys/unix"
)
// Ptr converts a Go byte array to a pointer to the start of the array.
@@ -126,3 +130,22 @@ func EffectiveUser() (*user.User, error) {
func IsUserRoot() bool {
return os.Geteuid() == 0
}
+
+// IsKernelVersionAtLeast returns true if the Linux kernel version is at least
+// major.minor. If something goes wrong it assumes false.
+func IsKernelVersionAtLeast(major, minor int) bool {
+ var uname unix.Utsname
+ if err := unix.Uname(&uname); err != nil {
+ log.Printf("Uname failed [%v], assuming old kernel", err)
+ return false
+ }
+ release := string(uname.Release[:])
+ log.Printf("Kernel version is %s", release)
+ var actualMajor, actualMinor int
+ if n, _ := fmt.Sscanf(release, "%d.%d", &actualMajor, &actualMinor); n != 2 {
+ log.Printf("Unrecognized uname format %q, assuming old kernel", release)
+ return false
+ }
+ return actualMajor > major ||
+ (actualMajor == major && actualMinor >= minor)
+}
diff --git a/util/util_test.go b/util/util_test.go
index 7739edd..70e7070 100644
--- a/util/util_test.go
+++ b/util/util_test.go
@@ -91,3 +91,10 @@ func TestNeverErrorNoPanic(t *testing.T) {
NeverError(nil)
}
+
+func TestIsKernelVersionAtLeast(t *testing.T) {
+ // Even just running Go requires at least v2.6.23, so...
+ if !IsKernelVersionAtLeast(2, 6) {
+ t.Error("IsKernelVersionAtLeast() is broken")
+ }
+}