diff --git a/init/Android.mk b/init/Android.mk index 4bfd743dd..442a5f301 100644 --- a/init/Android.mk +++ b/init/Android.mk @@ -46,6 +46,7 @@ LOCAL_CPPFLAGS := $(init_cflags) LOCAL_SRC_FILES:= \ action.cpp \ capabilities.cpp \ + descriptors.cpp \ import_parser.cpp \ init_parser.cpp \ log.cpp \ @@ -125,8 +126,10 @@ LOCAL_SRC_FILES := \ LOCAL_SHARED_LIBRARIES += \ libcutils \ libbase \ + libselinux \ LOCAL_STATIC_LIBRARIES := libinit LOCAL_SANITIZE := integer LOCAL_CLANG := true +LOCAL_CPPFLAGS := -Wall -Wextra -Werror include $(BUILD_NATIVE_TEST) diff --git a/init/descriptors.cpp b/init/descriptors.cpp new file mode 100644 index 000000000..10aae8882 --- /dev/null +++ b/init/descriptors.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2016 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 "descriptors.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "init.h" +#include "log.h" +#include "util.h" + +DescriptorInfo::DescriptorInfo(const std::string& name, const std::string& type, uid_t uid, + gid_t gid, int perm, const std::string& context) + : name_(name), type_(type), uid_(uid), gid_(gid), perm_(perm), context_(context) { +} + +DescriptorInfo::~DescriptorInfo() { +} + +std::ostream& operator<<(std::ostream& os, const DescriptorInfo& info) { + return os << " descriptors " << info.name_ << " " << info.type_ << " " << std::oct << info.perm_; +} + +bool DescriptorInfo::operator==(const DescriptorInfo& other) const { + return name_ == other.name_ && type_ == other.type_ && key() == other.key(); +} + +void DescriptorInfo::CreateAndPublish(const std::string& globalContext) const { + // Create + const std::string& contextStr = context_.empty() ? globalContext : context_; + int fd = Create(contextStr); + if (fd < 0) return; + + // Publish + std::string publishedName = key() + name_; + std::for_each(publishedName.begin(), publishedName.end(), + [] (char& c) { c = isalnum(c) ? c : '_'; }); + + std::string val = android::base::StringPrintf("%d", fd); + add_environment(publishedName.c_str(), val.c_str()); + + // make sure we don't close on exec + fcntl(fd, F_SETFD, 0); +} + +void DescriptorInfo::Clean() const { +} + +SocketInfo::SocketInfo(const std::string& name, const std::string& type, uid_t uid, + gid_t gid, int perm, const std::string& context) + : DescriptorInfo(name, type, uid, gid, perm, context) { +} + +void SocketInfo::Clean() const { + unlink(android::base::StringPrintf(ANDROID_SOCKET_DIR "/%s", name().c_str()).c_str()); +} + +int SocketInfo::Create(const std::string& context) const { + int flags = ((type() == "stream" ? SOCK_STREAM : + (type() == "dgram" ? SOCK_DGRAM : + SOCK_SEQPACKET))); + return create_socket(name().c_str(), flags, perm(), uid(), gid(), context.c_str()); +} + +const std::string SocketInfo::key() const { + return ANDROID_SOCKET_ENV_PREFIX; +} + +FileInfo::FileInfo(const std::string& name, const std::string& type, uid_t uid, + gid_t gid, int perm, const std::string& context) + : DescriptorInfo(name, type, uid, gid, perm, context) { +} + +int FileInfo::Create(const std::string& context) const { + int flags = ((type() == "r" ? O_RDONLY : + (type() == "w" ? (O_WRONLY | O_CREAT) : + (O_RDWR | O_CREAT)))); + return create_file(name().c_str(), flags, perm(), uid(), gid(), context.c_str()); +} + +const std::string FileInfo::key() const { + return ANDROID_FILE_ENV_PREFIX; +} diff --git a/init/descriptors.h b/init/descriptors.h new file mode 100644 index 000000000..ff276fbc0 --- /dev/null +++ b/init/descriptors.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2016 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. + */ + + +#ifndef _INIT_DESCRIPTORS_H +#define _INIT_DESCRIPTORS_H + +#include + +#include + +class DescriptorInfo { + public: + DescriptorInfo(const std::string& name, const std::string& type, uid_t uid, + gid_t gid, int perm, const std::string& context); + virtual ~DescriptorInfo(); + + friend std::ostream& operator<<(std::ostream& os, const class DescriptorInfo& info); + bool operator==(const DescriptorInfo& other) const; + + void CreateAndPublish(const std::string& globalContext) const; + virtual void Clean() const; + + protected: + const std::string& name() const { return name_; } + const std::string& type() const { return type_; } + uid_t uid() const { return uid_; } + gid_t gid() const { return gid_; } + int perm() const { return perm_; } + const std::string& context() const { return context_; } + + private: + std::string name_; + std::string type_; + uid_t uid_; + gid_t gid_; + int perm_; + std::string context_; + + virtual int Create(const std::string& globalContext) const = 0; + virtual const std::string key() const = 0; +}; + +std::ostream& operator<<(std::ostream& os, const DescriptorInfo& info); + +class SocketInfo : public DescriptorInfo { + public: + SocketInfo(const std::string& name, const std::string& type, uid_t uid, + gid_t gid, int perm, const std::string& context); + void Clean() const override; + private: + virtual int Create(const std::string& context) const override; + virtual const std::string key() const override; +}; + +class FileInfo : public DescriptorInfo { + public: + FileInfo(const std::string& name, const std::string& type, uid_t uid, + gid_t gid, int perm, const std::string& context); + private: + virtual int Create(const std::string& context) const override; + virtual const std::string key() const override; +}; + +#endif diff --git a/init/readme.txt b/init/readme.txt index e01faa9ca..5173ca692 100644 --- a/init/readme.txt +++ b/init/readme.txt @@ -141,12 +141,20 @@ setenv Set the environment variable to in the launched process. socket [ [ [ ] ] ] - Create a unix domain socket named /dev/socket/ and pass - its fd to the launched process. must be "dgram", "stream" or "seqpacket". - User and group default to 0. - 'seclabel' is the SELinux security context for the socket. - It defaults to the service security context, as specified by seclabel or - computed based on the service executable file security context. + Create a unix domain socket named /dev/socket/ and pass its fd to the + launched process. must be "dgram", "stream" or "seqpacket". User and + group default to 0. 'seclabel' is the SELinux security context for the + socket. It defaults to the service security context, as specified by + seclabel or computed based on the service executable file security context. + For native executables see libcutils android_get_control_socket(). + +file [ [ [ ] ] ] + Open/Create a file path and pass its fd to the launched process. must + be "r", "w" or "rw". User and group default to 0. 'seclabel' is the SELinux + security context for the file if it must be created. It defaults to the + service security context, as specified by seclabel or computed based on the + service executable file security context. For native executables see + libcutils android_get_control_file(). user Change to 'username' before exec'ing this service. diff --git a/init/service.cpp b/init/service.cpp index e052b45d3..9fa11b85b 100644 --- a/init/service.cpp +++ b/init/service.cpp @@ -36,7 +36,6 @@ #include #include #include -#include #include #include @@ -145,14 +144,6 @@ static void ExpandArgs(const std::vector& args, std::vector* strs->push_back(nullptr); } -SocketInfo::SocketInfo() : uid(0), gid(0), perm(0) { -} - -SocketInfo::SocketInfo(const std::string& name, const std::string& type, uid_t uid, - gid_t gid, int perm, const std::string& socketcon) - : name(name), type(type), uid(uid), gid(gid), perm(perm), socketcon(socketcon) { -} - ServiceEnvironmentInfo::ServiceEnvironmentInfo() { } @@ -213,20 +204,6 @@ void Service::KillProcessGroup(int signal) { } } -void Service::CreateSockets(const std::string& context) { - for (const auto& si : sockets_) { - int socket_type = ((si.type == "stream" ? SOCK_STREAM : - (si.type == "dgram" ? SOCK_DGRAM : - SOCK_SEQPACKET))); - const char* socketcon = !si.socketcon.empty() ? si.socketcon.c_str() : context.c_str(); - - int s = create_socket(si.name.c_str(), socket_type, si.perm, si.uid, si.gid, socketcon); - if (s >= 0) { - PublishSocket(si.name, s); - } - } -} - void Service::SetProcessAttributes() { // Keep capabilites on uid change. if (capabilities_.any() && uid_) { @@ -273,11 +250,9 @@ bool Service::Reap() { KillProcessGroup(SIGKILL); } - // Remove any sockets we may have created. - for (const auto& si : sockets_) { - std::string tmp = StringPrintf(ANDROID_SOCKET_DIR "/%s", si.name.c_str()); - unlink(tmp.c_str()); - } + // Remove any descriptor resources we may have created. + std::for_each(descriptors_.begin(), descriptors_.end(), + std::bind(&DescriptorInfo::Clean, std::placeholders::_1)); if (flags_ & SVC_EXEC) { LOG(INFO) << "SVC_EXEC pid " << pid_ << " finished..."; @@ -330,9 +305,8 @@ void Service::DumpState() const { LOG(INFO) << "service " << name_; LOG(INFO) << " class '" << classname_ << "'"; LOG(INFO) << " exec "<< android::base::Join(args_, " "); - for (const auto& si : sockets_) { - LOG(INFO) << " socket " << si.name << " " << si.type << " " << std::oct << si.perm; - } + std::for_each(descriptors_.begin(), descriptors_.end(), + [] (const auto& info) { LOG(INFO) << *info; }); } bool Service::ParseCapabilities(const std::vector& args, std::string* err) { @@ -469,20 +443,48 @@ bool Service::ParseSetenv(const std::vector& args, std::string* err return true; } -/* name type perm [ uid gid context ] */ +template +bool Service::AddDescriptor(const std::vector& args, std::string* err) { + int perm = args.size() > 3 ? std::strtoul(args[3].c_str(), 0, 8) : -1; + uid_t uid = args.size() > 4 ? decode_uid(args[4].c_str()) : 0; + gid_t gid = args.size() > 5 ? decode_uid(args[5].c_str()) : 0; + std::string context = args.size() > 6 ? args[6] : ""; + + auto descriptor = std::make_unique(args[1], args[2], uid, gid, perm, context); + + auto old = + std::find_if(descriptors_.begin(), descriptors_.end(), + [&descriptor] (const auto& other) { return descriptor.get() == other.get(); }); + + if (old != descriptors_.end()) { + *err = "duplicate descriptor " + args[1] + " " + args[2]; + return false; + } + + descriptors_.emplace_back(std::move(descriptor)); + return true; +} + +// name type perm [ uid gid context ] bool Service::ParseSocket(const std::vector& args, std::string* err) { if (args[2] != "dgram" && args[2] != "stream" && args[2] != "seqpacket") { *err = "socket type must be 'dgram', 'stream' or 'seqpacket'"; return false; } + return AddDescriptor(args, err); +} - int perm = std::strtoul(args[3].c_str(), 0, 8); - uid_t uid = args.size() > 4 ? decode_uid(args[4].c_str()) : 0; - gid_t gid = args.size() > 5 ? decode_uid(args[5].c_str()) : 0; - std::string socketcon = args.size() > 6 ? args[6] : ""; - - sockets_.emplace_back(args[1], args[2], uid, gid, perm, socketcon); - return true; +// name type perm [ uid gid context ] +bool Service::ParseFile(const std::vector& args, std::string* err) { + if (args[2] != "r" && args[2] != "w" && args[2] != "rw") { + *err = "file type must be 'r', 'w' or 'rw'"; + return false; + } + if ((args[1][0] != '/') || (args[1].find("../") != std::string::npos)) { + *err = "file name must not be relative"; + return false; + } + return AddDescriptor(args, err); } bool Service::ParseUser(const std::vector& args, std::string* err) { @@ -524,6 +526,7 @@ Service::OptionParserMap::Map& Service::OptionParserMap::map() const { {"seclabel", {1, 1, &Service::ParseSeclabel}}, {"setenv", {2, 2, &Service::ParseSetenv}}, {"socket", {3, 6, &Service::ParseSocket}}, + {"file", {2, 6, &Service::ParseFile}}, {"user", {1, 1, &Service::ParseUser}}, {"writepid", {1, kMax, &Service::ParseWritepid}}, }; @@ -613,7 +616,8 @@ bool Service::Start() { add_environment(ei.name.c_str(), ei.value.c_str()); } - CreateSockets(scon); + std::for_each(descriptors_.begin(), descriptors_.end(), + std::bind(&DescriptorInfo::CreateAndPublish, std::placeholders::_1, scon)); std::string pid_str = StringPrintf("%d", getpid()); for (const auto& file : writepid_files_) { @@ -787,15 +791,6 @@ void Service::OpenConsole() const { close(fd); } -void Service::PublishSocket(const std::string& name, int fd) const { - std::string key = StringPrintf(ANDROID_SOCKET_ENV_PREFIX "%s", name.c_str()); - std::string val = StringPrintf("%d", fd); - add_environment(key.c_str(), val.c_str()); - - /* make sure we don't close-on-exec */ - fcntl(fd, F_SETFD, 0); -} - int ServiceManager::exec_count_ = 0; ServiceManager::ServiceManager() { diff --git a/init/service.h b/init/service.h index 7f035eff2..d9e8f57c3 100644 --- a/init/service.h +++ b/init/service.h @@ -27,6 +27,7 @@ #include "action.h" #include "capabilities.h" +#include "descriptors.h" #include "init_parser.h" #include "keyword_map.h" @@ -48,18 +49,6 @@ class Action; class ServiceManager; -struct SocketInfo { - SocketInfo(); - SocketInfo(const std::string& name, const std::string& type, uid_t uid, - gid_t gid, int perm, const std::string& socketcon); - std::string name; - std::string type; - uid_t uid; - gid_t gid; - int perm; - std::string socketcon; -}; - struct ServiceEnvironmentInfo { ServiceEnvironmentInfo(); ServiceEnvironmentInfo(const std::string& name, const std::string& value); @@ -113,9 +102,7 @@ private: void StopOrReset(int how); void ZapStdio() const; void OpenConsole() const; - void PublishSocket(const std::string& name, int fd) const; void KillProcessGroup(int signal); - void CreateSockets(const std::string& scon); void SetProcessAttributes(); bool ParseCapabilities(const std::vector& args, std::string *err); @@ -134,9 +121,13 @@ private: bool ParseSeclabel(const std::vector& args, std::string* err); bool ParseSetenv(const std::vector& args, std::string* err); bool ParseSocket(const std::vector& args, std::string* err); + bool ParseFile(const std::vector& args, std::string* err); bool ParseUser(const std::vector& args, std::string* err); bool ParseWritepid(const std::vector& args, std::string* err); + template + bool AddDescriptor(const std::vector& args, std::string* err); + std::string name_; std::string classname_; std::string console_; @@ -155,7 +146,7 @@ private: std::string seclabel_; - std::vector sockets_; + std::vector> descriptors_; std::vector envvars_; Action onrestart_; // Commands to execute on restart. diff --git a/init/util.cpp b/init/util.cpp index 660a66f57..b280244d3 100644 --- a/init/util.cpp +++ b/init/util.cpp @@ -14,32 +14,32 @@ * limitations under the License. */ +#include +#include +#include +#include +#include #include #include #include #include -#include -#include -#include #include -#include -#include +#include -#include #include +#include +#include #include #include -#include #include #include #include +#include #include - /* for ANDROID_SOCKET_* */ #include -#include #include "init.h" #include "log.h" @@ -164,6 +164,76 @@ out_close: return -1; } +/* + * create_file - opens and creates a file as dictated in init.rc. + * This file is inherited by the daemon. We communicate the file + * descriptor's value via the environment variable ANDROID_FILE_ + */ +int create_file(const char *path, int flags, mode_t perm, uid_t uid, + gid_t gid, const char *filecon) +{ + char *secontext = NULL; + int ret; + + if (filecon) { + if (setsockcreatecon(filecon) == -1) { + PLOG(ERROR) << "setsockcreatecon(\"" << filecon << "\") failed"; + return -1; + } + } else if (sehandle) { + ret = selabel_lookup(sehandle, &secontext, path, perm); + if (ret != -1) { + ret = setfscreatecon(secontext); + if (ret == -1) { + freecon(secontext); + PLOG(ERROR) << "setfscreatecon(\"" << secontext << "\") failed"; + return -1; + } + } + } + + int fd = TEMP_FAILURE_RETRY(open(path, flags | O_NDELAY, perm)); + + if (filecon) { + setsockcreatecon(NULL); + lsetfilecon(path, filecon); + } else { + setfscreatecon(NULL); + freecon(secontext); + } + + if (fd == -1) { + PLOG(ERROR) << "Failed to open/create file '" << path << "'"; + goto out_close; + } + + if (!(flags & O_NDELAY)) fcntl(fd, F_SETFD, flags); + + ret = lchown(path, uid, gid); + if (ret) { + PLOG(ERROR) << "Failed to lchown file '" << path << "'"; + goto out_close; + } + if (perm != static_cast(-1)) { + ret = fchmodat(AT_FDCWD, path, perm, AT_SYMLINK_NOFOLLOW); + if (ret) { + PLOG(ERROR) << "Failed to fchmodat file '" << path << "'"; + goto out_close; + } + } + + LOG(INFO) << "Created file '" << path << "'" + << ", mode " << std::oct << perm << std::dec + << ", user " << uid + << ", group " << gid; + + return fd; + +out_close: + if (fd >= 0) close(fd); + return -1; +} + bool read_file(const char* path, std::string* content) { content->clear(); diff --git a/init/util.h b/init/util.h index b83b9a004..b7531cc5d 100644 --- a/init/util.h +++ b/init/util.h @@ -27,6 +27,8 @@ int create_socket(const char *name, int type, mode_t perm, uid_t uid, gid_t gid, const char *socketcon); +int create_file(const char *path, int mode, mode_t perm, + uid_t uid, gid_t gid, const char *filecon); bool read_file(const char* path, std::string* content); int write_file(const char* path, const char* content); diff --git a/init/util_test.cpp b/init/util_test.cpp index 228954b44..6ecbf908c 100644 --- a/init/util_test.cpp +++ b/init/util_test.cpp @@ -17,7 +17,15 @@ #include "util.h" #include +#include +#include +#include +#include +#include + +#include #include +#include TEST(util, read_file_ENOENT) { std::string s("hello"); @@ -41,3 +49,51 @@ TEST(util, decode_uid) { EXPECT_EQ(UINT_MAX, decode_uid("toot")); EXPECT_EQ(123U, decode_uid("123")); } + +struct selabel_handle *sehandle; + +TEST(util, create_file) { + if (!sehandle) sehandle = selinux_android_file_context_handle(); + + static const char path[] = "/data/local/tmp/util.create_file.test"; + static const char key[] = ANDROID_FILE_ENV_PREFIX "_data_local_tmp_util_create_file_test"; + EXPECT_EQ(unsetenv(key), 0); + unlink(path); + + int fd; + uid_t uid = decode_uid("logd"); + gid_t gid = decode_uid("system"); + mode_t perms = S_IRWXU | S_IWGRP | S_IRGRP | S_IROTH; + static const char context[] = "u:object_r:misc_logd_file:s0"; + EXPECT_GE(fd = create_file(path, O_RDWR | O_CREAT, perms, uid, gid, context), 0); + if (fd < 0) return; + static const char hello[] = "hello world\n"; + static const ssize_t len = strlen(hello); + EXPECT_EQ(write(fd, hello, len), len); + char buffer[sizeof(hello)]; + memset(buffer, 0, sizeof(buffer)); + EXPECT_GE(lseek(fd, 0, SEEK_SET), 0); + EXPECT_EQ(read(fd, buffer, sizeof(buffer)), len); + EXPECT_EQ(strcmp(hello, buffer), 0); + char val[32]; + snprintf(val, sizeof(val), "%d", fd); + EXPECT_EQ(android_get_control_file(path), -1); + setenv(key, val, true); + EXPECT_EQ(android_get_control_file(path), fd); + close(fd); + EXPECT_EQ(android_get_control_file(path), -1); + EXPECT_EQ(unsetenv(key), 0); + struct stat st; + EXPECT_EQ(stat(path, &st), 0); + EXPECT_EQ(st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO), perms); + EXPECT_EQ(st.st_uid, uid); + EXPECT_EQ(st.st_gid, gid); + security_context_t con; + EXPECT_GE(getfilecon(path, &con), 0); + EXPECT_NE(con, static_cast(NULL)); + if (con) { + EXPECT_EQ(context, std::string(con)); + } + freecon(con); + EXPECT_EQ(unlink(path), 0); +}