platform_system_core/init/property_service.cpp
Elliott Hughes 929f407076 Switch init to epoll.
Not just because it's what the cool kids are doing --- it also lets us
simplify the inner loop and decouple it from whatever systems want to
be woken to perform some activity if there's data to be read on some fd.

Currently this is just used to clean up the existing signal handling,
keychord, and property service code.

Change-Id: I4d7541a2c4386957ad877df69e3be08b96a7dec5
2015-04-24 21:13:44 -07:00

532 lines
15 KiB
C++

/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <stdarg.h>
#include <dirent.h>
#include <limits.h>
#include <errno.h>
#include <sys/poll.h>
#include <memory>
#include <cutils/misc.h>
#include <cutils/sockets.h>
#include <cutils/multiuser.h>
#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
#include <sys/_system_properties.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/select.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/mman.h>
#include <private/android_filesystem_config.h>
#include <selinux/selinux.h>
#include <selinux/label.h>
#include "property_service.h"
#include "init.h"
#include "util.h"
#include "log.h"
#define PERSISTENT_PROPERTY_DIR "/data/property"
static int persistent_properties_loaded = 0;
static bool property_area_initialized = false;
static int property_set_fd = -1;
struct workspace {
size_t size;
int fd;
};
static workspace pa_workspace;
void property_init() {
if (property_area_initialized) {
return;
}
property_area_initialized = true;
if (__system_property_area_init()) {
return;
}
pa_workspace.size = 0;
pa_workspace.fd = open(PROP_FILENAME, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
if (pa_workspace.fd == -1) {
ERROR("Failed to open %s: %s\n", PROP_FILENAME, strerror(errno));
return;
}
}
static int check_mac_perms(const char *name, char *sctx)
{
if (is_selinux_enabled() <= 0)
return 1;
char *tctx = NULL;
int result = 0;
if (!sctx)
goto err;
if (!sehandle_prop)
goto err;
if (selabel_lookup(sehandle_prop, &tctx, name, 1) != 0)
goto err;
if (selinux_check_access(sctx, tctx, "property_service", "set", (void*) name) == 0)
result = 1;
freecon(tctx);
err:
return result;
}
static int check_control_mac_perms(const char *name, char *sctx)
{
/*
* Create a name prefix out of ctl.<service name>
* The new prefix allows the use of the existing
* property service backend labeling while avoiding
* mislabels based on true property prefixes.
*/
char ctl_name[PROP_VALUE_MAX+4];
int ret = snprintf(ctl_name, sizeof(ctl_name), "ctl.%s", name);
if (ret < 0 || (size_t) ret >= sizeof(ctl_name))
return 0;
return check_mac_perms(ctl_name, sctx);
}
/*
* Checks permissions for setting system properties.
* Returns 1 if uid allowed, 0 otherwise.
*/
static int check_perms(const char *name, char *sctx)
{
if(!strncmp(name, "ro.", 3))
name +=3;
return check_mac_perms(name, sctx);
}
int __property_get(const char *name, char *value)
{
return __system_property_get(name, value);
}
static void write_persistent_property(const char *name, const char *value)
{
char tempPath[PATH_MAX];
char path[PATH_MAX];
int fd;
snprintf(tempPath, sizeof(tempPath), "%s/.temp.XXXXXX", PERSISTENT_PROPERTY_DIR);
fd = mkstemp(tempPath);
if (fd < 0) {
ERROR("Unable to write persistent property to temp file %s: %s\n", tempPath, strerror(errno));
return;
}
write(fd, value, strlen(value));
fsync(fd);
close(fd);
snprintf(path, sizeof(path), "%s/%s", PERSISTENT_PROPERTY_DIR, name);
if (rename(tempPath, path)) {
unlink(tempPath);
ERROR("Unable to rename persistent property file %s to %s\n", tempPath, path);
}
}
static bool is_legal_property_name(const char* name, size_t namelen)
{
size_t i;
if (namelen >= PROP_NAME_MAX) return false;
if (namelen < 1) return false;
if (name[0] == '.') return false;
if (name[namelen - 1] == '.') return false;
/* Only allow alphanumeric, plus '.', '-', or '_' */
/* Don't allow ".." to appear in a property name */
for (i = 0; i < namelen; i++) {
if (name[i] == '.') {
// i=0 is guaranteed to never have a dot. See above.
if (name[i-1] == '.') return false;
continue;
}
if (name[i] == '_' || name[i] == '-') continue;
if (name[i] >= 'a' && name[i] <= 'z') continue;
if (name[i] >= 'A' && name[i] <= 'Z') continue;
if (name[i] >= '0' && name[i] <= '9') continue;
return false;
}
return true;
}
static int property_set_impl(const char* name, const char* value) {
size_t namelen = strlen(name);
size_t valuelen = strlen(value);
if (!is_legal_property_name(name, namelen)) return -1;
if (valuelen >= PROP_VALUE_MAX) return -1;
prop_info* pi = (prop_info*) __system_property_find(name);
if(pi != 0) {
/* ro.* properties may NEVER be modified once set */
if(!strncmp(name, "ro.", 3)) return -1;
__system_property_update(pi, value, valuelen);
} else {
int rc = __system_property_add(name, namelen, value, valuelen);
if (rc < 0) {
return rc;
}
}
/* If name starts with "net." treat as a DNS property. */
if (strncmp("net.", name, strlen("net.")) == 0) {
if (strcmp("net.change", name) == 0) {
return 0;
}
/*
* The 'net.change' property is a special property used track when any
* 'net.*' property name is updated. It is _ONLY_ updated here. Its value
* contains the last updated 'net.*' property.
*/
property_set("net.change", name);
} else if (persistent_properties_loaded &&
strncmp("persist.", name, strlen("persist.")) == 0) {
/*
* Don't write properties to disk until after we have read all default properties
* to prevent them from being overwritten by default values.
*/
write_persistent_property(name, value);
} else if (strcmp("selinux.reload_policy", name) == 0 &&
strcmp("1", value) == 0) {
selinux_reload_policy();
}
property_changed(name, value);
return 0;
}
int property_set(const char* name, const char* value) {
int rc = property_set_impl(name, value);
if (rc == -1) {
ERROR("property_set(\"%s\", \"%s\") failed\n", name, value);
}
return rc;
}
static void handle_property_set_fd()
{
prop_msg msg;
int s;
int r;
struct ucred cr;
struct sockaddr_un addr;
socklen_t addr_size = sizeof(addr);
socklen_t cr_size = sizeof(cr);
char * source_ctx = NULL;
struct pollfd ufds[1];
const int timeout_ms = 2 * 1000; /* Default 2 sec timeout for caller to send property. */
int nr;
if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) {
return;
}
/* Check socket options here */
if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
close(s);
ERROR("Unable to receive socket options\n");
return;
}
ufds[0].fd = s;
ufds[0].events = POLLIN;
ufds[0].revents = 0;
nr = TEMP_FAILURE_RETRY(poll(ufds, 1, timeout_ms));
if (nr == 0) {
ERROR("sys_prop: timeout waiting for uid=%d to send property message.\n", cr.uid);
close(s);
return;
} else if (nr < 0) {
ERROR("sys_prop: error waiting for uid=%d to send property message: %s\n", cr.uid, strerror(errno));
close(s);
return;
}
r = TEMP_FAILURE_RETRY(recv(s, &msg, sizeof(msg), MSG_DONTWAIT));
if(r != sizeof(prop_msg)) {
ERROR("sys_prop: mis-match msg size received: %d expected: %zu: %s\n",
r, sizeof(prop_msg), strerror(errno));
close(s);
return;
}
switch(msg.cmd) {
case PROP_MSG_SETPROP:
msg.name[PROP_NAME_MAX-1] = 0;
msg.value[PROP_VALUE_MAX-1] = 0;
if (!is_legal_property_name(msg.name, strlen(msg.name))) {
ERROR("sys_prop: illegal property name. Got: \"%s\"\n", msg.name);
close(s);
return;
}
getpeercon(s, &source_ctx);
if(memcmp(msg.name,"ctl.",4) == 0) {
// Keep the old close-socket-early behavior when handling
// ctl.* properties.
close(s);
if (check_control_mac_perms(msg.value, source_ctx)) {
handle_control_message((char*) msg.name + 4, (char*) msg.value);
} else {
ERROR("sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%d\n",
msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid);
}
} else {
if (check_perms(msg.name, source_ctx)) {
property_set((char*) msg.name, (char*) msg.value);
} else {
ERROR("sys_prop: permission denied uid:%d name:%s\n",
cr.uid, msg.name);
}
// Note: bionic's property client code assumes that the
// property server will not close the socket until *AFTER*
// the property is written to memory.
close(s);
}
freecon(source_ctx);
break;
default:
close(s);
break;
}
}
void get_property_workspace(int *fd, int *sz)
{
*fd = pa_workspace.fd;
*sz = pa_workspace.size;
}
static void load_properties_from_file(const char *, const char *);
/*
* Filter is used to decide which properties to load: NULL loads all keys,
* "ro.foo.*" is a prefix match, and "ro.foo.bar" is an exact match.
*/
static void load_properties(char *data, const char *filter)
{
char *key, *value, *eol, *sol, *tmp, *fn;
size_t flen = 0;
if (filter) {
flen = strlen(filter);
}
sol = data;
while ((eol = strchr(sol, '\n'))) {
key = sol;
*eol++ = 0;
sol = eol;
while (isspace(*key)) key++;
if (*key == '#') continue;
tmp = eol - 2;
while ((tmp > key) && isspace(*tmp)) *tmp-- = 0;
if (!strncmp(key, "import ", 7) && flen == 0) {
fn = key + 7;
while (isspace(*fn)) fn++;
key = strchr(fn, ' ');
if (key) {
*key++ = 0;
while (isspace(*key)) key++;
}
load_properties_from_file(fn, key);
} else {
value = strchr(key, '=');
if (!value) continue;
*value++ = 0;
tmp = value - 2;
while ((tmp > key) && isspace(*tmp)) *tmp-- = 0;
while (isspace(*value)) value++;
if (flen > 0) {
if (filter[flen - 1] == '*') {
if (strncmp(key, filter, flen - 1)) continue;
} else {
if (strcmp(key, filter)) continue;
}
}
property_set(key, value);
}
}
}
/*
* Filter is used to decide which properties to load: NULL loads all keys,
* "ro.foo.*" is a prefix match, and "ro.foo.bar" is an exact match.
*/
static void load_properties_from_file(const char* filename, const char* filter) {
Timer t;
std::string data;
if (read_file(filename, &data)) {
load_properties(&data[0], filter);
}
NOTICE("(Loading properties from %s took %.2fs.)\n", filename, t.duration());
}
static void load_persistent_properties() {
persistent_properties_loaded = 1;
std::unique_ptr<DIR, int(*)(DIR*)> dir(opendir(PERSISTENT_PROPERTY_DIR), closedir);
if (!dir) {
ERROR("Unable to open persistent property directory \"%s\": %s\n",
PERSISTENT_PROPERTY_DIR, strerror(errno));
return;
}
struct dirent* entry;
while ((entry = readdir(dir.get())) != NULL) {
if (strncmp("persist.", entry->d_name, strlen("persist."))) {
continue;
}
if (entry->d_type != DT_REG) {
continue;
}
// Open the file and read the property value.
int fd = openat(dirfd(dir.get()), entry->d_name, O_RDONLY | O_NOFOLLOW);
if (fd == -1) {
ERROR("Unable to open persistent property file \"%s\": %s\n",
entry->d_name, strerror(errno));
continue;
}
struct stat sb;
if (fstat(fd, &sb) == -1) {
ERROR("fstat on property file \"%s\" failed: %s\n", entry->d_name, strerror(errno));
close(fd);
continue;
}
// File must not be accessible to others, be owned by root/root, and
// not be a hard link to any other file.
if (((sb.st_mode & (S_IRWXG | S_IRWXO)) != 0) || (sb.st_uid != 0) || (sb.st_gid != 0) ||
(sb.st_nlink != 1)) {
ERROR("skipping insecure property file %s (uid=%u gid=%u nlink=%u mode=%o)\n",
entry->d_name, (unsigned int)sb.st_uid, (unsigned int)sb.st_gid,
(unsigned int)sb.st_nlink, sb.st_mode);
close(fd);
continue;
}
char value[PROP_VALUE_MAX];
int length = read(fd, value, sizeof(value) - 1);
if (length >= 0) {
value[length] = 0;
property_set(entry->d_name, value);
} else {
ERROR("Unable to read persistent property file %s: %s\n",
entry->d_name, strerror(errno));
}
close(fd);
}
}
void property_load_boot_defaults() {
load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT, NULL);
}
bool properties_initialized() {
return property_area_initialized;
}
static void load_override_properties() {
if (ALLOW_LOCAL_PROP_OVERRIDE) {
char debuggable[PROP_VALUE_MAX];
int ret = property_get("ro.debuggable", debuggable);
if (ret && (strcmp(debuggable, "1") == 0)) {
load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE, NULL);
}
}
}
/* When booting an encrypted system, /data is not mounted when the
* property service is started, so any properties stored there are
* not loaded. Vold triggers init to load these properties once it
* has mounted /data.
*/
void load_persist_props(void) {
load_override_properties();
/* Read persistent properties after all default values have been loaded. */
load_persistent_properties();
}
void load_all_props() {
load_properties_from_file(PROP_PATH_SYSTEM_BUILD, NULL);
load_properties_from_file(PROP_PATH_VENDOR_BUILD, NULL);
load_properties_from_file(PROP_PATH_BOOTIMAGE_BUILD, NULL);
load_properties_from_file(PROP_PATH_FACTORY, "ro.*");
load_override_properties();
/* Read persistent properties after all default values have been loaded. */
load_persistent_properties();
}
void start_property_service() {
property_set_fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
0666, 0, 0, NULL);
if (property_set_fd == -1) {
ERROR("start_property_service socket creation failed: %s\n", strerror(errno));
exit(1);
}
listen(property_set_fd, 8);
register_epoll_handler(property_set_fd, handle_property_set_fd);
}