init: support setting rlimits per service

Add a new service option, `rlimit` that allows a given rlimit to be
set for a specific service instead of globally.

Use the same parsing, now allowing text such as 'cpu' or 'rtprio'
instead of relying on the enum value for the `setrlimit` builtin
command as well.

Bug: 63882119
Bug: 64894637

Test: boot bullhead, run a test app that attempts to set its rtprio to
      95, see that the priority set fails normally but passes when
      `rlimit rtprio 99 99` is used as its service option.
      See that this fails when `rlimit rtprio 50 50` is used as well.
Test: new unit tests

Change-Id: I4a13ca20e8529937d8b4bc11718ffaaf77523a52
This commit is contained in:
Tom Cherry 2017-08-25 10:39:25 -07:00
parent df3e89be94
commit 7ac013de7e
8 changed files with 276 additions and 14 deletions

View file

@ -76,6 +76,7 @@ cc_library_static {
"security.cpp",
"selinux.cpp",
"service.cpp",
"rlimit_parser.cpp",
"tokenizer.cpp",
"uevent_listener.cpp",
"ueventd_parser.cpp",
@ -163,6 +164,7 @@ cc_test {
"init_test.cpp",
"property_service_test.cpp",
"result_test.cpp",
"rlimit_parser_test.cpp",
"service_test.cpp",
"ueventd_test.cpp",
"util_test.cpp",

View file

@ -216,6 +216,12 @@ runs the service.
http://man7.org/linux/man-pages/man7/capabilities.7.html for a list of Linux
capabilities.
`setrlimit <resource> <cur> <max>`
> This applies the given rlimit to the service. rlimits are inherited by child
processes, so this effectively applies the given rlimit to the process tree
started by this service.
It is parsed similarly to the setrlimit command specified below.
`seclabel <seclabel>`
> Change to 'seclabel' before exec'ing this service.
Primarily for use by services run from the rootfs, e.g. ueventd, adbd.
@ -455,7 +461,11 @@ Commands
within _value_.
`setrlimit <resource> <cur> <max>`
> Set the rlimit for a resource.
> Set the rlimit for a resource. This applies to all processes launched after
the limit is set. It is intended to be set early in init and applied globally.
_resource_ is best specified using its text representation ('cpu', 'rtio', etc
or 'RLIM_CPU', 'RLIM_RTIO', etc). It also may be specified as the int value
that the resource enum corresponds to.
`start <service>`
> Start a service running if it is not already running.

View file

@ -63,6 +63,7 @@
#include "parser.h"
#include "property_service.h"
#include "reboot.h"
#include "rlimit_parser.h"
#include "service.h"
#include "signal_handler.h"
#include "util.h"
@ -563,20 +564,10 @@ static Result<Success> do_setprop(const std::vector<std::string>& args) {
}
static Result<Success> do_setrlimit(const std::vector<std::string>& args) {
int resource;
if (!android::base::ParseInt(args[1], &resource)) {
return Error() << "unable to parse resource, " << args[1];
}
auto rlimit = ParseRlimit(args);
if (!rlimit) return rlimit.error();
struct rlimit limit;
if (!android::base::ParseUint(args[2], &limit.rlim_cur)) {
return Error() << "unable to parse rlim_cur, " << args[2];
}
if (!android::base::ParseUint(args[3], &limit.rlim_max)) {
return Error() << "unable to parse rlim_max, " << args[3];
}
if (setrlimit(resource, &limit) == -1) {
if (setrlimit(rlimit->first, &rlimit->second) == -1) {
return ErrnoError() << "setrlimit failed";
}
return Success();

78
init/rlimit_parser.cpp Normal file
View file

@ -0,0 +1,78 @@
/*
* Copyright (C) 2017 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 "rlimit_parser.h"
#include <android-base/parseint.h>
#include <android-base/strings.h>
using android::base::EqualsIgnoreCase;
using android::base::ParseInt;
using android::base::ParseUint;
using android::base::StartsWith;
namespace android {
namespace init {
// Builtins and service definitions both have their arguments start at 1 and finish at 3.
Result<std::pair<int, rlimit>> ParseRlimit(const std::vector<std::string>& args) {
static const std::vector<std::pair<const char*, int>> text_to_resources = {
{"cpu", 0}, {"fsize", 1}, {"data", 2}, {"stack", 3},
{"core", 4}, {"rss", 5}, {"nproc", 6}, {"nofile", 7},
{"memlock", 8}, {"as", 9}, {"locks", 10}, {"sigpending", 11},
{"msgqueue", 12}, {"nice", 13}, {"rtprio", 14}, {"rttime", 15},
};
int resource;
if (ParseInt(args[1], &resource)) {
if (resource >= RLIM_NLIMITS) {
return Error() << "Resource '" << args[1] << "' over the maximum resource value '"
<< RLIM_NLIMITS << "'";
} else if (resource < 0) {
return Error() << "Resource '" << args[1] << "' below the minimum resource value '0'";
}
} else {
std::string resource_string;
if (StartsWith(args[1], "RLIM_")) {
resource_string = args[1].substr(5);
} else {
resource_string = args[1];
}
auto it = std::find_if(text_to_resources.begin(), text_to_resources.end(),
[&resource_string](const auto& entry) {
return EqualsIgnoreCase(resource_string, entry.first);
});
if (it == text_to_resources.end()) {
return Error() << "Could not parse resource '" << args[1] << "'";
}
resource = it->second;
}
rlimit limit;
if (!ParseUint(args[2], &limit.rlim_cur)) {
return Error() << "Could not parse soft limit '" << args[2] << "'";
}
if (!ParseUint(args[3], &limit.rlim_max)) {
return Error() << "Could not parse hard limit '" << args[3] << "'";
}
return {resource, limit};
}
} // namespace init
} // namespace android

35
init/rlimit_parser.h Normal file
View file

@ -0,0 +1,35 @@
/*
* Copyright (C) 2017 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_RLIMIT_PARSER_H
#define _INIT_RLIMIT_PARSER_H
#include <sys/resource.h>
#include <string>
#include <vector>
#include "result.h"
namespace android {
namespace init {
Result<std::pair<int, rlimit>> ParseRlimit(const std::vector<std::string>& args);
} // namespace init
} // namespace android
#endif

126
init/rlimit_parser_test.cpp Normal file
View file

@ -0,0 +1,126 @@
/*
* Copyright (C) 2017 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 "rlimit_parser.h"
#include <iostream>
#include <gtest/gtest.h>
namespace android {
namespace init {
void TestRlimitSuccess(std::vector<std::string> input,
const std::pair<int, rlimit>& expected_result) {
input.emplace(input.begin(), "");
ASSERT_EQ(4U, input.size());
auto result = ParseRlimit(input);
ASSERT_TRUE(result) << "input: " << input[1];
const auto& [resource, rlimit] = *result;
const auto& [expected_resource, expected_rlimit] = expected_result;
EXPECT_EQ(expected_resource, resource);
EXPECT_EQ(expected_rlimit.rlim_cur, rlimit.rlim_cur);
EXPECT_EQ(expected_rlimit.rlim_max, rlimit.rlim_max);
}
void TestRlimitFailure(std::vector<std::string> input, const std::string& expected_result) {
input.emplace(input.begin(), "");
ASSERT_EQ(4U, input.size());
auto result = ParseRlimit(input);
ASSERT_FALSE(result) << "input: " << input[1];
EXPECT_EQ(expected_result, result.error_string());
EXPECT_EQ(0, result.error_errno());
}
TEST(rlimit, RlimitSuccess) {
const std::vector<std::pair<std::vector<std::string>, std::pair<int, rlimit>>>
inputs_and_results = {
{{"cpu", "10", "10"}, {0, {10, 10}}},
{{"fsize", "10", "10"}, {1, {10, 10}}},
{{"data", "10", "10"}, {2, {10, 10}}},
{{"stack", "10", "10"}, {3, {10, 10}}},
{{"core", "10", "10"}, {4, {10, 10}}},
{{"rss", "10", "10"}, {5, {10, 10}}},
{{"nproc", "10", "10"}, {6, {10, 10}}},
{{"nofile", "10", "10"}, {7, {10, 10}}},
{{"memlock", "10", "10"}, {8, {10, 10}}},
{{"as", "10", "10"}, {9, {10, 10}}},
{{"locks", "10", "10"}, {10, {10, 10}}},
{{"sigpending", "10", "10"}, {11, {10, 10}}},
{{"msgqueue", "10", "10"}, {12, {10, 10}}},
{{"nice", "10", "10"}, {13, {10, 10}}},
{{"rtprio", "10", "10"}, {14, {10, 10}}},
{{"rttime", "10", "10"}, {15, {10, 10}}},
{{"RLIM_CPU", "10", "10"}, {0, {10, 10}}},
{{"RLIM_FSIZE", "10", "10"}, {1, {10, 10}}},
{{"RLIM_DATA", "10", "10"}, {2, {10, 10}}},
{{"RLIM_STACK", "10", "10"}, {3, {10, 10}}},
{{"RLIM_CORE", "10", "10"}, {4, {10, 10}}},
{{"RLIM_RSS", "10", "10"}, {5, {10, 10}}},
{{"RLIM_NPROC", "10", "10"}, {6, {10, 10}}},
{{"RLIM_NOFILE", "10", "10"}, {7, {10, 10}}},
{{"RLIM_MEMLOCK", "10", "10"}, {8, {10, 10}}},
{{"RLIM_AS", "10", "10"}, {9, {10, 10}}},
{{"RLIM_LOCKS", "10", "10"}, {10, {10, 10}}},
{{"RLIM_SIGPENDING", "10", "10"}, {11, {10, 10}}},
{{"RLIM_MSGQUEUE", "10", "10"}, {12, {10, 10}}},
{{"RLIM_NICE", "10", "10"}, {13, {10, 10}}},
{{"RLIM_RTPRIO", "10", "10"}, {14, {10, 10}}},
{{"RLIM_RTTIME", "10", "10"}, {15, {10, 10}}},
{{"0", "10", "10"}, {0, {10, 10}}},
{{"1", "10", "10"}, {1, {10, 10}}},
{{"2", "10", "10"}, {2, {10, 10}}},
{{"3", "10", "10"}, {3, {10, 10}}},
{{"4", "10", "10"}, {4, {10, 10}}},
{{"5", "10", "10"}, {5, {10, 10}}},
{{"6", "10", "10"}, {6, {10, 10}}},
{{"7", "10", "10"}, {7, {10, 10}}},
{{"8", "10", "10"}, {8, {10, 10}}},
{{"9", "10", "10"}, {9, {10, 10}}},
{{"10", "10", "10"}, {10, {10, 10}}},
{{"11", "10", "10"}, {11, {10, 10}}},
{{"12", "10", "10"}, {12, {10, 10}}},
{{"13", "10", "10"}, {13, {10, 10}}},
{{"14", "10", "10"}, {14, {10, 10}}},
{{"15", "10", "10"}, {15, {10, 10}}},
};
for (const auto& [input, expected_result] : inputs_and_results) {
TestRlimitSuccess(input, expected_result);
}
}
TEST(rlimit, RlimitFailure) {
const std::vector<std::pair<std::vector<std::string>, std::string>> inputs_and_results = {
{{"-4", "10", "10"}, "Resource '-4' below the minimum resource value '0'"},
{{"100", "10", "10"}, "Resource '100' over the maximum resource value '16'"},
{{"bad_string", "10", "10"}, "Could not parse resource 'bad_string'"},
{{"RLIM_", "10", "10"}, "Could not parse resource 'RLIM_'"},
{{"cpu", "abc", "10"}, "Could not parse soft limit 'abc'"},
{{"cpu", "10", "abc"}, "Could not parse hard limit 'abc'"},
};
for (const auto& [input, expected_result] : inputs_and_results) {
TestRlimitFailure(input, expected_result);
}
}
} // namespace init
} // namespace android

View file

@ -43,6 +43,7 @@
#include "init.h"
#include "property_service.h"
#include "rlimit_parser.h"
#include "util.h"
using android::base::boot_clock;
@ -216,6 +217,12 @@ void Service::KillProcessGroup(int signal) {
}
void Service::SetProcessAttributes() {
for (const auto& rlimit : rlimits_) {
if (setrlimit(rlimit.first, &rlimit.second) == -1) {
LOG(FATAL) << StringPrintf("setrlimit(%d, {rlim_cur=%ld, rlim_max=%ld}) failed",
rlimit.first, rlimit.second.rlim_cur, rlimit.second.rlim_max);
}
}
// Keep capabilites on uid change.
if (capabilities_.any() && uid_) {
// If Android is running in a container, some securebits might already
@ -489,6 +496,14 @@ Result<Success> Service::ParseMemcgSoftLimitInBytes(const std::vector<std::strin
return Success();
}
Result<Success> Service::ParseProcessRlimit(const std::vector<std::string>& args) {
auto rlimit = ParseRlimit(args);
if (!rlimit) return rlimit.error();
rlimits_.emplace_back(*rlimit);
return Success();
}
Result<Success> Service::ParseSeclabel(const std::vector<std::string>& args) {
seclabel_ = args[1];
return Success();
@ -609,6 +624,7 @@ const Service::OptionParserMap::Map& Service::OptionParserMap::map() const {
{"memcg.limit_in_bytes",
{1, 1, &Service::ParseMemcgLimitInBytes}},
{"namespace", {1, 2, &Service::ParseNamespace}},
{"rlimit", {3, 3, &Service::ParseProcessRlimit}},
{"seclabel", {1, 1, &Service::ParseSeclabel}},
{"setenv", {2, 2, &Service::ParseSetenv}},
{"shutdown", {1, 1, &Service::ParseShutdown}},

View file

@ -17,6 +17,7 @@
#ifndef _INIT_SERVICE_H
#define _INIT_SERVICE_H
#include <sys/resource.h>
#include <sys/types.h>
#include <memory>
@ -138,6 +139,7 @@ class Service {
Result<Success> ParseMemcgSoftLimitInBytes(const std::vector<std::string>& args);
Result<Success> ParseMemcgSwappiness(const std::vector<std::string>& args);
Result<Success> ParseNamespace(const std::vector<std::string>& args);
Result<Success> ParseProcessRlimit(const std::vector<std::string>& args);
Result<Success> ParseSeclabel(const std::vector<std::string>& args);
Result<Success> ParseSetenv(const std::vector<std::string>& args);
Result<Success> ParseShutdown(const std::vector<std::string>& args);
@ -195,6 +197,8 @@ class Service {
unsigned long start_order_;
std::vector<std::pair<int, rlimit>> rlimits_;
std::vector<std::string> args_;
};