da40c00137
Also make important events in init's life NOTICE rather than INFO, and ensure that NOTICE events actually make it to the kernel log. Also fix the logging so that if you have a printf format string error, the compiler now catches it. Also give messages from init, ueventd, and watchdogd distinct tags. (Previously they'd all call themselves "init", and dmesg doesn't include pids, so you couldn't untangle them.) Also include the tag in SELinux messages. Bug: 19544788 Change-Id: Ica6daea065bfdb80155c52c0b06f346a7df208fe
535 lines
15 KiB
C++
535 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;
|
|
}
|
|
|
|
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_SYSTEM_DEFAULT, 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() {
|
|
int fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0, NULL);
|
|
if (fd == -1) return;
|
|
|
|
fcntl(fd, F_SETFD, FD_CLOEXEC);
|
|
fcntl(fd, F_SETFL, O_NONBLOCK);
|
|
|
|
listen(fd, 8);
|
|
property_set_fd = fd;
|
|
}
|
|
|
|
int get_property_set_fd() {
|
|
return property_set_fd;
|
|
}
|