Update OTA to understand SELinux filesystem labels

Make fs_config aware of SELinux contexts, and output the context
whenever we output the UID / GID / file perms.

Pass the selinux context to the set_perm2() and set_perm2_recursive()
calls. When the OTA script fixes up filesystem permissions, it will
also fix up the SELinux context on the files.

Bug: 8985290
Change-Id: I6419b64c06309a93ac6b2f2cf9fc7f8815adeaf3
This commit is contained in:
Nick Kralevich
2013-07-17 17:43:09 -07:00
parent 0a2c858bb6
commit fbbd79530a
5 changed files with 108 additions and 32 deletions

View File

@@ -1142,6 +1142,7 @@ $(BUILT_TARGET_FILES_PACKAGE): \
$(INSTALLED_CACHEIMAGE_TARGET) \ $(INSTALLED_CACHEIMAGE_TARGET) \
$(INSTALLED_VENDORIMAGE_TARGET) \ $(INSTALLED_VENDORIMAGE_TARGET) \
$(INSTALLED_ANDROID_INFO_TXT_TARGET) \ $(INSTALLED_ANDROID_INFO_TXT_TARGET) \
$(SELINUX_FC) \
$(built_ota_tools) \ $(built_ota_tools) \
$(APKCERTS_FILE) \ $(APKCERTS_FILE) \
$(HOST_OUT_EXECUTABLES)/fs_config \ $(HOST_OUT_EXECUTABLES)/fs_config \
@@ -1233,9 +1234,9 @@ endif
@# Zip everything up, preserving symlinks @# Zip everything up, preserving symlinks
$(hide) (cd $(zip_root) && zip -qry ../$(notdir $@) .) $(hide) (cd $(zip_root) && zip -qry ../$(notdir $@) .)
@# Run fs_config on all the system, boot ramdisk, and recovery ramdisk files in the zip, and save the output @# Run fs_config on all the system, boot ramdisk, and recovery ramdisk files in the zip, and save the output
$(hide) zipinfo -1 $@ | awk 'BEGIN { FS="SYSTEM/" } /^SYSTEM\// {print "system/" $$2}' | $(HOST_OUT_EXECUTABLES)/fs_config > $(zip_root)/META/filesystem_config.txt $(hide) zipinfo -1 $@ | awk 'BEGIN { FS="SYSTEM/" } /^SYSTEM\// {print "system/" $$2}' | $(HOST_OUT_EXECUTABLES)/fs_config -S $(SELINUX_FC) > $(zip_root)/META/filesystem_config.txt
$(hide) zipinfo -1 $@ | awk 'BEGIN { FS="BOOT/RAMDISK/" } /^BOOT\/RAMDISK\// {print $$2}' | $(HOST_OUT_EXECUTABLES)/fs_config > $(zip_root)/META/boot_filesystem_config.txt $(hide) zipinfo -1 $@ | awk 'BEGIN { FS="BOOT/RAMDISK/" } /^BOOT\/RAMDISK\// {print $$2}' | $(HOST_OUT_EXECUTABLES)/fs_config -S $(SELINUX_FC) > $(zip_root)/META/boot_filesystem_config.txt
$(hide) zipinfo -1 $@ | awk 'BEGIN { FS="RECOVERY/RAMDISK/" } /^RECOVERY\/RAMDISK\// {print $$2}' | $(HOST_OUT_EXECUTABLES)/fs_config > $(zip_root)/META/recovery_filesystem_config.txt $(hide) zipinfo -1 $@ | awk 'BEGIN { FS="RECOVERY/RAMDISK/" } /^RECOVERY\/RAMDISK\// {print $$2}' | $(HOST_OUT_EXECUTABLES)/fs_config -S $(SELINUX_FC) > $(zip_root)/META/recovery_filesystem_config.txt
$(hide) (cd $(zip_root) && zip -q ../$(notdir $@) META/*filesystem_config.txt) $(hide) (cd $(zip_root) && zip -q ../$(notdir $@) META/*filesystem_config.txt)
.PHONY: target-files-package .PHONY: target-files-package

View File

@@ -17,6 +17,7 @@ include $(CLEAR_VARS)
LOCAL_SRC_FILES := fs_config.c LOCAL_SRC_FILES := fs_config.c
LOCAL_MODULE := fs_config LOCAL_MODULE := fs_config
LOCAL_STATIC_LIBRARIES := libselinux
LOCAL_FORCE_STATIC_EXECUTABLE := true LOCAL_FORCE_STATIC_EXECUTABLE := true
include $(BUILD_HOST_EXECUTABLE) include $(BUILD_HOST_EXECUTABLE)

View File

@@ -15,11 +15,16 @@
*/ */
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <errno.h> #include <errno.h>
#include <unistd.h> #include <unistd.h>
#include <string.h> #include <string.h>
#include <selinux/selinux.h>
#include <selinux/label.h>
#include <selinux/android.h>
#include "private/android_filesystem_config.h" #include "private/android_filesystem_config.h"
// This program takes a list of files and directories (indicated by a // This program takes a list of files and directories (indicated by a
@@ -37,11 +42,48 @@
// system/etc/dbus.conf 1002 1002 440 // system/etc/dbus.conf 1002 1002 440
// data/app 1000 1000 771 // data/app 1000 1000 771
// //
// or if -S is used:
//
// system/etc/dbus.conf 1002 1002 440 u:object_r:system_file:s0
// data/app 1000 1000 771 u:object_r:apk_data_file:s0
//
// Note that the output will omit the trailing slash from // Note that the output will omit the trailing slash from
// directories. // directories.
static struct selabel_handle* get_sehnd(const char* context_file) {
struct selinux_opt seopts[] = { { SELABEL_OPT_PATH, context_file } };
struct selabel_handle* sehnd = selabel_open(SELABEL_CTX_FILE, seopts, 1);
if (!sehnd) {
perror("error running selabel_open");
exit(EXIT_FAILURE);
}
return sehnd;
}
static void usage() {
fprintf(stderr, "Usage: fs_config [-S context_file]\n");
}
int main(int argc, char** argv) { int main(int argc, char** argv) {
char buffer[1024]; char buffer[1024];
const char* context_file = NULL;
struct selabel_handle* sehnd = NULL;
int opt;
while((opt = getopt(argc, argv, "S:")) != -1) {
switch(opt) {
case 'S':
context_file = optarg;
break;
default:
usage();
exit(EXIT_FAILURE);
}
}
if (context_file != NULL) {
sehnd = get_sehnd(context_file);
}
while (fgets(buffer, 1023, stdin) != NULL) { while (fgets(buffer, 1023, stdin) != NULL) {
int is_dir = 0; int is_dir = 0;
@@ -64,7 +106,35 @@ int main(int argc, char** argv) {
unsigned uid = 0, gid = 0, mode = 0; unsigned uid = 0, gid = 0, mode = 0;
uint64_t capabilities; uint64_t capabilities;
fs_config(buffer, is_dir, &uid, &gid, &mode, &capabilities); fs_config(buffer, is_dir, &uid, &gid, &mode, &capabilities);
printf("%s %d %d %o\n", buffer, uid, gid, mode); printf("%s %d %d %o", buffer, uid, gid, mode);
if (sehnd != NULL) {
size_t buffer_strlen = strnlen(buffer, sizeof(buffer));
if (buffer_strlen >= sizeof(buffer)) {
fprintf(stderr, "non null terminated buffer, aborting\n");
exit(EXIT_FAILURE);
}
size_t full_name_size = buffer_strlen + 2;
char* full_name = (char*) malloc(full_name_size);
if (full_name == NULL) {
perror("malloc");
exit(EXIT_FAILURE);
}
full_name[0] = '/';
strncpy(full_name + 1, buffer, full_name_size - 1);
full_name[full_name_size - 1] = '\0';
char* secontext;
if (selabel_lookup(sehnd, &secontext, full_name, ( mode | (is_dir ? S_IFDIR : S_IFREG)))) {
secontext = strdup("u:object_r:unlabeled:s0");
}
printf(" %s", secontext);
free(full_name);
freecon(secontext);
}
printf("\n");
} }
return 0; return 0;
} }

View File

@@ -217,14 +217,14 @@ class EdifyGenerator(object):
else: else:
raise ValueError("don't know how to write \"%s\" partitions" % (p.fs_type,)) raise ValueError("don't know how to write \"%s\" partitions" % (p.fs_type,))
def SetPermissions(self, fn, uid, gid, mode): def SetPermissions(self, fn, uid, gid, mode, secontext):
"""Set file ownership and permissions.""" """Set file ownership and permissions."""
self.script.append('set_perm(%d, %d, 0%o, "%s");' % (uid, gid, mode, fn)) self.script.append('set_perm2(%d, %d, 0%o, "%s", "%s");' % (uid, gid, mode, secontext, fn))
def SetPermissionsRecursive(self, fn, uid, gid, dmode, fmode): def SetPermissionsRecursive(self, fn, uid, gid, dmode, fmode, secontext):
"""Recursively set path ownership and permissions.""" """Recursively set path ownership and permissions."""
self.script.append('set_perm_recursive(%d, %d, 0%o, 0%o, "%s");' self.script.append('set_perm2_recursive(%d, %d, 0%o, 0%o, "%s", "%s");'
% (uid, gid, dmode, fmode, fn)) % (uid, gid, dmode, fmode, secontext, fn))
def MakeSymlinks(self, symlink_list): def MakeSymlinks(self, symlink_list):
"""Create symlinks, given a list of (dest, link) pairs.""" """Create symlinks, given a list of (dest, link) pairs."""

View File

@@ -117,6 +117,7 @@ class Item:
self.uid = None self.uid = None
self.gid = None self.gid = None
self.mode = None self.mode = None
self.secontext = None
self.dir = dir self.dir = dir
if name: if name:
@@ -166,63 +167,66 @@ class Item:
for line in output.split("\n"): for line in output.split("\n"):
if not line: continue if not line: continue
name, uid, gid, mode = line.split() name, uid, gid, mode, secontext = line.split()
i = cls.ITEMS.get(name, None) i = cls.ITEMS.get(name, None)
if i is not None: if i is not None:
i.uid = int(uid) i.uid = int(uid)
i.gid = int(gid) i.gid = int(gid)
i.mode = int(mode, 8) i.mode = int(mode, 8)
i.secontext = secontext
if i.dir: if i.dir:
i.children.sort(key=lambda i: i.name) i.children.sort(key=lambda i: i.name)
# set metadata for the files generated by this script. # set metadata for the files generated by this script.
i = cls.ITEMS.get("system/recovery-from-boot.p", None) i = cls.ITEMS.get("system/recovery-from-boot.p", None)
if i: i.uid, i.gid, i.mode = 0, 0, 0644 if i: i.uid, i.gid, i.mode, i.secontext = 0, 0, 0644, "u:object_r:system_file:s0"
i = cls.ITEMS.get("system/etc/install-recovery.sh", None) i = cls.ITEMS.get("system/etc/install-recovery.sh", None)
if i: i.uid, i.gid, i.mode = 0, 0, 0544 if i: i.uid, i.gid, i.mode, i.secontext = 0, 0, 0544, "u:object_r:system_file:s0"
def CountChildMetadata(self): def CountChildMetadata(self):
"""Count up the (uid, gid, mode) tuples for all children and """Count up the (uid, gid, mode, secontext) tuples for all children and
determine the best strategy for using set_perm_recursive and determine the best strategy for using set_perm_recursive and
set_perm to correctly chown/chmod all the files to their desired set_perm to correctly chown/chmod all the files to their desired
values. Recursively calls itself for all descendants. values. Recursively calls itself for all descendants.
Returns a dict of {(uid, gid, dmode, fmode): count} counting up Returns a dict of {(uid, gid, dmode, fmode, secontext): count} counting up
all descendants of this node. (dmode or fmode may be None.) Also all descendants of this node. (dmode or fmode may be None.) Also
sets the best_subtree of each directory Item to the (uid, gid, sets the best_subtree of each directory Item to the (uid, gid,
dmode, fmode) tuple that will match the most descendants of that dmode, fmode, secontext) tuple that will match the most descendants of that
Item. Item.
""" """
assert self.dir assert self.dir
d = self.descendants = {(self.uid, self.gid, self.mode, None): 1} d = self.descendants = {(self.uid, self.gid, self.mode, None, self.secontext): 1}
for i in self.children: for i in self.children:
if i.dir: if i.dir:
for k, v in i.CountChildMetadata().iteritems(): for k, v in i.CountChildMetadata().iteritems():
d[k] = d.get(k, 0) + v d[k] = d.get(k, 0) + v
else: else:
k = (i.uid, i.gid, None, i.mode) k = (i.uid, i.gid, None, i.mode, i.secontext)
d[k] = d.get(k, 0) + 1 d[k] = d.get(k, 0) + 1
# Find the (uid, gid, dmode, fmode) tuple that matches the most # Find the (uid, gid, dmode, fmode, secontext) tuple that matches the most
# descendants. # descendants.
# First, find the (uid, gid) pair that matches the most # First, find the (uid, gid) pair that matches the most
# descendants. # descendants.
ug = {} ug = {}
for (uid, gid, _, _), count in d.iteritems(): for (uid, gid, _, _, _), count in d.iteritems():
ug[(uid, gid)] = ug.get((uid, gid), 0) + count ug[(uid, gid)] = ug.get((uid, gid), 0) + count
ug = MostPopularKey(ug, (0, 0)) ug = MostPopularKey(ug, (0, 0))
# Now find the dmode and fmode that match the most descendants # Now find the dmode, fmode and secontext that match the most descendants
# with that (uid, gid), and choose those. # with that (uid, gid), and choose those.
best_dmode = (0, 0755) best_dmode = (0, 0755)
best_fmode = (0, 0644) best_fmode = (0, 0644)
best_secontext = (0, "u:object_r:system_file:s0")
for k, count in d.iteritems(): for k, count in d.iteritems():
if k[:2] != ug: continue if k[:2] != ug: continue
if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2]) if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2])
if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3]) if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3])
self.best_subtree = ug + (best_dmode[1], best_fmode[1]) if k[4] is not None and count >= best_secontext[0]: best_secontext = (count, k[4])
self.best_subtree = ug + (best_dmode[1], best_fmode[1], best_secontext[1])
return d return d
@@ -234,7 +238,7 @@ class Item:
self.CountChildMetadata() self.CountChildMetadata()
def recurse(item, current): def recurse(item, current):
# current is the (uid, gid, dmode, fmode) tuple that the current # current is the (uid, gid, dmode, fmode, secontext) tuple that the current
# item (and all its children) have already been set to. We only # item (and all its children) have already been set to. We only
# need to issue set_perm/set_perm_recursive commands if we're # need to issue set_perm/set_perm_recursive commands if we're
# supposed to be something different. # supposed to be something different.
@@ -244,17 +248,17 @@ class Item:
current = item.best_subtree current = item.best_subtree
if item.uid != current[0] or item.gid != current[1] or \ if item.uid != current[0] or item.gid != current[1] or \
item.mode != current[2]: item.mode != current[2] or item.secontext != current[4]:
script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode) script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode, item.secontext)
for i in item.children: for i in item.children:
recurse(i, current) recurse(i, current)
else: else:
if item.uid != current[0] or item.gid != current[1] or \ if item.uid != current[0] or item.gid != current[1] or \
item.mode != current[3]: item.mode != current[3] or item.secontext != current[4]:
script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode) script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode, item.secontext)
recurse(self, (-1, -1, -1, -1)) recurse(self, (-1, -1, -1, -1, "u:object_r:unlabeled:s0"))
def CopySystemFiles(input_zip, output_zip=None, def CopySystemFiles(input_zip, output_zip=None,
@@ -733,7 +737,7 @@ def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
for item in deferred_patch_list: for item in deferred_patch_list:
fn, tf, sf, size, _ = item fn, tf, sf, size, _ = item
script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, sf.sha1, "patch/"+fn+".p") script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, sf.sha1, "patch/"+fn+".p")
script.SetPermissions("/system/build.prop", 0, 0, 0644) script.SetPermissions("/system/build.prop", 0, 0, 0644, "u:object_r:system_file:s0")
script.AddToZip(target_zip, output_zip) script.AddToZip(target_zip, output_zip)
WriteMetadata(metadata, output_zip) WriteMetadata(metadata, output_zip)