Make encryption action an argument to mkdir
FscryptSetDirectoryPolicy no longer tries to infer the action from the
filename. Well mostly; it still assumes top-level directories in /data
should be encrypted unless the mkdir arguments say otherwise, but
it warns.
Bug: 26641735
Test: boot, check log messages
Change-Id: Id6d2cea7fb856f17323897d85cf6190c981b443c
diff --git a/init/README.md b/init/README.md
index cdf3487..e8df4ec 100644
--- a/init/README.md
+++ b/init/README.md
@@ -505,12 +505,23 @@
> Used to mark the point right after /data is mounted. Used to implement the
`class_reset_post_data` and `class_start_post_data` commands.
-`mkdir <path> [mode] [owner] [group]`
+`mkdir <path> [<mode>] [<owner>] [<group>] [encryption=<action>] [key=<key>]`
> Create a directory at _path_, optionally with the given mode, owner, and
group. If not provided, the directory is created with permissions 755 and
owned by the root user and root group. If provided, the mode, owner and group
will be updated if the directory exists already.
+ > _action_ can be one of:
+ * `None`: take no encryption action; directory will be encrypted if parent is.
+ * `Require`: encrypt directory, abort boot process if encryption fails
+ * `Attempt`: try to set an encryption policy, but continue if it fails
+ * `DeleteIfNecessary`: recursively delete directory if necessary to set
+ encryption policy.
+
+ > _key_ can be one of:
+ * `ref`: use the systemwide DE key
+ * `per_boot_ref`: use the key freshly generated on each boot.
+
`mount_all <fstab> [ <path> ]\* [--<option>]`
> Calls fs\_mgr\_mount\_all on the given fs\_mgr-format fstab with optional
options "early" and "late".
diff --git a/init/builtins.cpp b/init/builtins.cpp
index b2c6461..67c05b2 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -364,67 +364,52 @@
return {};
}
-// mkdir <path> [mode] [owner] [group]
+// mkdir <path> [mode] [owner] [group] [<option> ...]
static Result<void> do_mkdir(const BuiltinArguments& args) {
- mode_t mode = 0755;
- Result<uid_t> uid = -1;
- Result<gid_t> gid = -1;
-
- switch (args.size()) {
- case 5:
- gid = DecodeUid(args[4]);
- if (!gid) {
- return Error() << "Unable to decode GID for '" << args[4] << "': " << gid.error();
- }
- FALLTHROUGH_INTENDED;
- case 4:
- uid = DecodeUid(args[3]);
- if (!uid) {
- return Error() << "Unable to decode UID for '" << args[3] << "': " << uid.error();
- }
- FALLTHROUGH_INTENDED;
- case 3:
- mode = std::strtoul(args[2].c_str(), 0, 8);
- FALLTHROUGH_INTENDED;
- case 2:
- break;
- default:
- return Error() << "Unexpected argument count: " << args.size();
+ auto options = ParseMkdir(args.args);
+ if (!options) return options.error();
+ std::string ref_basename;
+ if (options->ref_option == "ref") {
+ ref_basename = fscrypt_key_ref;
+ } else if (options->ref_option == "per_boot_ref") {
+ ref_basename = fscrypt_key_per_boot_ref;
+ } else {
+ return Error() << "Unknown key option: '" << options->ref_option << "'";
}
- std::string target = args[1];
+
struct stat mstat;
- if (lstat(target.c_str(), &mstat) != 0) {
+ if (lstat(options->target.c_str(), &mstat) != 0) {
if (errno != ENOENT) {
- return ErrnoError() << "lstat() failed on " << target;
+ return ErrnoError() << "lstat() failed on " << options->target;
}
- if (!make_dir(target, mode)) {
- return ErrnoErrorIgnoreEnoent() << "mkdir() failed on " << target;
+ if (!make_dir(options->target, options->mode)) {
+ return ErrnoErrorIgnoreEnoent() << "mkdir() failed on " << options->target;
}
- if (lstat(target.c_str(), &mstat) != 0) {
- return ErrnoError() << "lstat() failed on new " << target;
+ if (lstat(options->target.c_str(), &mstat) != 0) {
+ return ErrnoError() << "lstat() failed on new " << options->target;
}
}
if (!S_ISDIR(mstat.st_mode)) {
- return Error() << "Not a directory on " << target;
+ return Error() << "Not a directory on " << options->target;
}
- bool needs_chmod = (mstat.st_mode & ~S_IFMT) != mode;
- if ((*uid != static_cast<uid_t>(-1) && *uid != mstat.st_uid) ||
- (*gid != static_cast<gid_t>(-1) && *gid != mstat.st_gid)) {
- if (lchown(target.c_str(), *uid, *gid) == -1) {
- return ErrnoError() << "lchown failed on " << target;
+ bool needs_chmod = (mstat.st_mode & ~S_IFMT) != options->mode;
+ if ((options->uid != static_cast<uid_t>(-1) && options->uid != mstat.st_uid) ||
+ (options->gid != static_cast<gid_t>(-1) && options->gid != mstat.st_gid)) {
+ if (lchown(options->target.c_str(), options->uid, options->gid) == -1) {
+ return ErrnoError() << "lchown failed on " << options->target;
}
// chown may have cleared S_ISUID and S_ISGID, chmod again
needs_chmod = true;
}
if (needs_chmod) {
- if (fchmodat(AT_FDCWD, target.c_str(), mode, AT_SYMLINK_NOFOLLOW) == -1) {
- return ErrnoError() << "fchmodat() failed on " << target;
+ if (fchmodat(AT_FDCWD, options->target.c_str(), options->mode, AT_SYMLINK_NOFOLLOW) == -1) {
+ return ErrnoError() << "fchmodat() failed on " << options->target;
}
}
if (fscrypt_is_native()) {
- if (fscrypt_set_directory_policy(target)) {
+ if (!FscryptSetDirectoryPolicy(ref_basename, options->fscrypt_action, options->target)) {
return reboot_into_recovery(
- {"--prompt_and_wipe_data", "--reason=set_policy_failed:"s + target});
+ {"--prompt_and_wipe_data", "--reason=set_policy_failed:"s + options->target});
}
}
return {};
@@ -589,8 +574,8 @@
return reboot_into_recovery(options);
/* If reboot worked, there is no return. */
} else if (code == FS_MGR_MNTALL_DEV_FILE_ENCRYPTED) {
- if (fscrypt_install_keyring()) {
- return Error() << "fscrypt_install_keyring() failed";
+ if (!FscryptInstallKeyring()) {
+ return Error() << "FscryptInstallKeyring() failed";
}
property_set("ro.crypto.state", "encrypted");
property_set("ro.crypto.type", "file");
@@ -600,8 +585,8 @@
ActionManager::GetInstance().QueueEventTrigger("nonencrypted");
return {};
} else if (code == FS_MGR_MNTALL_DEV_IS_METADATA_ENCRYPTED) {
- if (fscrypt_install_keyring()) {
- return Error() << "fscrypt_install_keyring() failed";
+ if (!FscryptInstallKeyring()) {
+ return Error() << "FscryptInstallKeyring() failed";
}
property_set("ro.crypto.state", "encrypted");
property_set("ro.crypto.type", "file");
@@ -611,8 +596,8 @@
ActionManager::GetInstance().QueueEventTrigger("nonencrypted");
return {};
} else if (code == FS_MGR_MNTALL_DEV_NEEDS_METADATA_ENCRYPTION) {
- if (fscrypt_install_keyring()) {
- return Error() << "fscrypt_install_keyring() failed";
+ if (!FscryptInstallKeyring()) {
+ return Error() << "FscryptInstallKeyring() failed";
}
property_set("ro.crypto.state", "encrypted");
property_set("ro.crypto.type", "file");
@@ -1257,7 +1242,7 @@
{"load_system_props", {0, 0, {false, do_load_system_props}}},
{"loglevel", {1, 1, {false, do_loglevel}}},
{"mark_post_data", {0, 0, {false, do_mark_post_data}}},
- {"mkdir", {1, 4, {true, do_mkdir}}},
+ {"mkdir", {1, 6, {true, do_mkdir}}},
// TODO: Do mount operations in vendor_init.
// mount_all is currently too complex to run in vendor_init as it queues action triggers,
// imports rc scripts, etc. It should be simplified and run in vendor_init context.
diff --git a/init/check_builtins.cpp b/init/check_builtins.cpp
index 2efaeea..9d23921 100644
--- a/init/check_builtins.cpp
+++ b/init/check_builtins.cpp
@@ -121,22 +121,10 @@
}
Result<void> check_mkdir(const BuiltinArguments& args) {
- if (args.size() >= 4) {
- if (!args[3].empty()) {
- auto uid = DecodeUid(args[3]);
- if (!uid) {
- return Error() << "Unable to decode UID for '" << args[3] << "': " << uid.error();
- }
- }
-
- if (args.size() == 5 && !args[4].empty()) {
- auto gid = DecodeUid(args[4]);
- if (!gid) {
- return Error() << "Unable to decode GID for '" << args[4] << "': " << gid.error();
- }
- }
+ auto options = ParseMkdir(args.args);
+ if (!options) {
+ return options.error();
}
-
return {};
}
diff --git a/init/fscrypt_init_extensions.cpp b/init/fscrypt_init_extensions.cpp
index 5fa07dd..7820f3d 100644
--- a/init/fscrypt_init_extensions.cpp
+++ b/init/fscrypt_init_extensions.cpp
@@ -41,19 +41,15 @@
using namespace android::fscrypt;
-static int set_policy_on(const std::string& ref_basename, const std::string& dir);
-
-int fscrypt_install_keyring() {
+bool FscryptInstallKeyring() {
key_serial_t device_keyring = add_key("keyring", "fscrypt", 0, 0, KEY_SPEC_SESSION_KEYRING);
if (device_keyring == -1) {
PLOG(ERROR) << "Failed to create keyring";
- return -1;
+ return false;
}
-
LOG(INFO) << "Keyring created with id " << device_keyring << " in process " << getpid();
-
- return 0;
+ return true;
}
// TODO(b/139378601): use a single central implementation of this.
@@ -97,102 +93,57 @@
}
}
-int fscrypt_set_directory_policy(const std::string& dir) {
- const std::string prefix = "/data/";
-
- if (!android::base::StartsWith(dir, prefix)) {
- return 0;
- }
-
- // Special-case /data/media/obb per b/64566063
- if (dir == "/data/media/obb") {
- // Try to set policy on this directory, but if it is non-empty this may fail.
- set_policy_on(fscrypt_key_ref, dir);
- return 0;
- }
-
- // Only set policy on first level /data directories
- // To make this less restrictive, consider using a policy file.
- // However this is overkill for as long as the policy is simply
- // to apply a global policy to all /data folders created via makedir
- if (dir.find_first_of('/', prefix.size()) != std::string::npos) {
- return 0;
- }
-
- // Special case various directories that must not be encrypted,
- // often because their subdirectories must be encrypted.
- // This isn't a nice way to do this, see b/26641735
- std::vector<std::string> directories_to_exclude = {
- "lost+found",
- "system_ce", "system_de",
- "misc_ce", "misc_de",
- "vendor_ce", "vendor_de",
- "media",
- "data", "user", "user_de",
- "apex", "preloads", "app-staging",
- "gsi",
- };
- for (const auto& d : directories_to_exclude) {
- if ((prefix + d) == dir) {
- LOG(INFO) << "Not setting policy on " << dir;
- return 0;
- }
- }
- std::vector<std::string> per_boot_directories = {
- "per_boot",
- };
- for (const auto& d : per_boot_directories) {
- if ((prefix + d) == dir) {
- LOG(INFO) << "Setting per_boot key on " << dir;
- return set_policy_on(fscrypt_key_per_boot_ref, dir);
- }
- }
- int err = set_policy_on(fscrypt_key_ref, dir);
- if (err == 0) {
- return 0;
- }
- // Empty these directories if policy setting fails.
- std::vector<std::string> wipe_on_failure = {
- "rollback", "rollback-observer", // b/139193659
- };
- for (const auto& d : wipe_on_failure) {
- if ((prefix + d) == dir) {
- LOG(ERROR) << "Setting policy failed, deleting: " << dir;
- delete_dir_contents(dir);
- err = set_policy_on(fscrypt_key_ref, dir);
- break;
- }
- }
- return err;
-}
-
-// Set an encryption policy on the given directory. The policy (key reference
+// Look up an encryption policy The policy (key reference
// and encryption options) to use is read from files that were written by vold.
-static int set_policy_on(const std::string& ref_basename, const std::string& dir) {
- EncryptionPolicy policy;
+static bool LookupPolicy(const std::string& ref_basename, EncryptionPolicy* policy) {
std::string ref_filename = std::string("/data") + ref_basename;
- if (!android::base::ReadFileToString(ref_filename, &policy.key_raw_ref)) {
- LOG(ERROR) << "Unable to read system policy to set on " << dir;
- return -1;
+ if (!android::base::ReadFileToString(ref_filename, &policy->key_raw_ref)) {
+ LOG(ERROR) << "Unable to read system policy with name " << ref_filename;
+ return false;
}
auto options_filename = std::string("/data") + fscrypt_key_mode;
std::string options_string;
if (!android::base::ReadFileToString(options_filename, &options_string)) {
LOG(ERROR) << "Cannot read encryption options string";
- return -1;
+ return false;
}
- if (!ParseOptions(options_string, &policy.options)) {
+ if (!ParseOptions(options_string, &policy->options)) {
LOG(ERROR) << "Invalid encryption options string: " << options_string;
- return -1;
+ return false;
}
+ return true;
+}
+static bool EnsurePolicyOrLog(const EncryptionPolicy& policy, const std::string& dir) {
if (!EnsurePolicy(policy, dir)) {
std::string ref_hex;
BytesToHex(policy.key_raw_ref, &ref_hex);
LOG(ERROR) << "Setting " << ref_hex << " policy on " << dir << " failed!";
- return -1;
+ return false;
}
+ return true;
+}
- return 0;
+static bool SetPolicyOn(const std::string& ref_basename, const std::string& dir) {
+ EncryptionPolicy policy;
+ if (!LookupPolicy(ref_basename, &policy)) return false;
+ if (!EnsurePolicyOrLog(policy, dir)) return false;
+ return true;
+}
+
+bool FscryptSetDirectoryPolicy(const std::string& ref_basename, FscryptAction action,
+ const std::string& dir) {
+ if (action == FscryptAction::kNone) {
+ return true;
+ }
+ if (SetPolicyOn(ref_basename, dir) || action == FscryptAction::kAttempt) {
+ return true;
+ }
+ if (action == FscryptAction::kDeleteIfNecessary) {
+ LOG(ERROR) << "Setting policy failed, deleting: " << dir;
+ delete_dir_contents(dir);
+ return SetPolicyOn(ref_basename, dir);
+ }
+ return false;
}
diff --git a/init/fscrypt_init_extensions.h b/init/fscrypt_init_extensions.h
index 2163ef6..d357bb2 100644
--- a/init/fscrypt_init_extensions.h
+++ b/init/fscrypt_init_extensions.h
@@ -18,5 +18,13 @@
#include <string>
-int fscrypt_install_keyring();
-int fscrypt_set_directory_policy(const std::string& dir);
+enum class FscryptAction {
+ kNone,
+ kAttempt,
+ kRequire,
+ kDeleteIfNecessary,
+};
+
+bool FscryptInstallKeyring();
+bool FscryptSetDirectoryPolicy(const std::string& ref_basename, FscryptAction action,
+ const std::string& dir);
diff --git a/init/util.cpp b/init/util.cpp
index 40db838..ada9e78 100644
--- a/init/util.cpp
+++ b/init/util.cpp
@@ -434,6 +434,142 @@
return {};
}
+static FscryptAction FscryptInferAction(const std::string& dir) {
+ const std::string prefix = "/data/";
+
+ if (!android::base::StartsWith(dir, prefix)) {
+ return FscryptAction::kNone;
+ }
+
+ // Special-case /data/media/obb per b/64566063
+ if (dir == "/data/media/obb") {
+ // Try to set policy on this directory, but if it is non-empty this may fail.
+ return FscryptAction::kAttempt;
+ }
+
+ // Only set policy on first level /data directories
+ // To make this less restrictive, consider using a policy file.
+ // However this is overkill for as long as the policy is simply
+ // to apply a global policy to all /data folders created via makedir
+ if (dir.find_first_of('/', prefix.size()) != std::string::npos) {
+ return FscryptAction::kNone;
+ }
+
+ // Special case various directories that must not be encrypted,
+ // often because their subdirectories must be encrypted.
+ // This isn't a nice way to do this, see b/26641735
+ std::vector<std::string> directories_to_exclude = {
+ "lost+found", "system_ce", "system_de", "misc_ce", "misc_de",
+ "vendor_ce", "vendor_de", "media", "data", "user",
+ "user_de", "apex", "preloads", "app-staging", "gsi",
+ };
+ for (const auto& d : directories_to_exclude) {
+ if ((prefix + d) == dir) {
+ return FscryptAction::kNone;
+ }
+ }
+ // Empty these directories if policy setting fails.
+ std::vector<std::string> wipe_on_failure = {
+ "rollback", "rollback-observer", // b/139193659
+ };
+ for (const auto& d : wipe_on_failure) {
+ if ((prefix + d) == dir) {
+ return FscryptAction::kDeleteIfNecessary;
+ }
+ }
+ return FscryptAction::kRequire;
+}
+
+Result<MkdirOptions> ParseMkdir(const std::vector<std::string>& args) {
+ mode_t mode = 0755;
+ Result<uid_t> uid = -1;
+ Result<gid_t> gid = -1;
+ FscryptAction fscrypt_inferred_action = FscryptInferAction(args[1]);
+ FscryptAction fscrypt_action = fscrypt_inferred_action;
+ std::string ref_option = "ref";
+ bool set_option_encryption = false;
+ bool set_option_key = false;
+
+ for (size_t i = 2; i < args.size(); i++) {
+ switch (i) {
+ case 2:
+ mode = std::strtoul(args[2].c_str(), 0, 8);
+ break;
+ case 3:
+ uid = DecodeUid(args[3]);
+ if (!uid) {
+ return Error()
+ << "Unable to decode UID for '" << args[3] << "': " << uid.error();
+ }
+ break;
+ case 4:
+ gid = DecodeUid(args[4]);
+ if (!gid) {
+ return Error()
+ << "Unable to decode GID for '" << args[4] << "': " << gid.error();
+ }
+ break;
+ default:
+ auto parts = android::base::Split(args[i], "=");
+ if (parts.size() != 2) {
+ return Error() << "Can't parse option: '" << args[i] << "'";
+ }
+ auto optname = parts[0];
+ auto optval = parts[1];
+ if (optname == "encryption") {
+ if (set_option_encryption) {
+ return Error() << "Duplicated option: '" << optname << "'";
+ }
+ if (optval == "Require") {
+ fscrypt_action = FscryptAction::kRequire;
+ } else if (optval == "None") {
+ fscrypt_action = FscryptAction::kNone;
+ } else if (optval == "Attempt") {
+ fscrypt_action = FscryptAction::kAttempt;
+ } else if (optval == "DeleteIfNecessary") {
+ fscrypt_action = FscryptAction::kDeleteIfNecessary;
+ } else {
+ return Error() << "Unknown encryption option: '" << optval << "'";
+ }
+ set_option_encryption = true;
+ } else if (optname == "key") {
+ if (set_option_key) {
+ return Error() << "Duplicated option: '" << optname << "'";
+ }
+ if (optval == "ref" || optval == "per_boot_ref") {
+ ref_option = optval;
+ } else {
+ return Error() << "Unknown key option: '" << optval << "'";
+ }
+ set_option_key = true;
+ } else {
+ return Error() << "Unknown option: '" << args[i] << "'";
+ }
+ }
+ }
+ if (set_option_key && fscrypt_action == FscryptAction::kNone) {
+ return Error() << "Key option set but encryption action is none";
+ }
+ const std::string prefix = "/data/";
+ if (StartsWith(args[1], prefix) &&
+ args[1].find_first_of('/', prefix.size()) == std::string::npos) {
+ if (!set_option_encryption) {
+ LOG(WARNING) << "Top-level directory needs encryption action, eg mkdir " << args[1]
+ << " <mode> <uid> <gid> encryption=Require";
+ }
+ if (fscrypt_action == FscryptAction::kNone) {
+ LOG(INFO) << "Not setting encryption policy on: " << args[1];
+ }
+ }
+ if (fscrypt_action != fscrypt_inferred_action) {
+ LOG(WARNING) << "Inferred action different from explicit one, expected "
+ << static_cast<int>(fscrypt_inferred_action) << " but got "
+ << static_cast<int>(fscrypt_action);
+ }
+
+ return MkdirOptions{args[1], mode, *uid, *gid, fscrypt_action, ref_option};
+}
+
Result<std::pair<int, std::vector<std::string>>> ParseRestorecon(
const std::vector<std::string>& args) {
struct flag_type {
diff --git a/init/util.h b/init/util.h
index 4cccefe..3d81d72 100644
--- a/init/util.h
+++ b/init/util.h
@@ -25,6 +25,7 @@
#include <android-base/chrono_utils.h>
+#include "fscrypt_init_extensions.h"
#include "result.h"
using android::base::boot_clock;
@@ -60,6 +61,17 @@
bool IsLegalPropertyName(const std::string& name);
Result<void> IsLegalPropertyValue(const std::string& name, const std::string& value);
+struct MkdirOptions {
+ std::string target;
+ mode_t mode;
+ uid_t uid;
+ gid_t gid;
+ FscryptAction fscrypt_action;
+ std::string ref_option;
+};
+
+Result<MkdirOptions> ParseMkdir(const std::vector<std::string>& args);
+
Result<std::pair<int, std::vector<std::string>>> ParseRestorecon(
const std::vector<std::string>& args);