blob: eb2d1b7ae6bc7e1afa47cb52e5ecd34b95b4731f [file] [log] [blame]
Dylan Reid6b590e62016-10-27 19:10:53 -07001// Copyright 2016 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Stephen Barber5f6dc9b2017-04-04 12:36:32 -07005#include "run_oci/container_config_parser.h"
Dylan Reid6b590e62016-10-27 19:10:53 -07006
Luis Hector Chavez6e9a5332017-10-26 14:41:49 -07007#include <linux/securebits.h>
Luis Hector Chavez8373b412017-07-10 12:51:07 -07008#include <sys/capability.h>
Luis Hector Chavez7c6fddf2017-10-24 15:39:41 -07009#include <sys/mount.h>
Dylan Reid93fa4602017-06-06 13:39:31 -070010#include <sys/resource.h>
Dylan Reid6b590e62016-10-27 19:10:53 -070011#include <unistd.h>
12
Luis Hector Chavez8e4dcc12017-06-27 12:54:47 -070013#include <map>
Ben Chanf43980b2017-03-10 11:31:46 -080014#include <regex> // NOLINT(build/c++11)
Dylan Reid6b590e62016-10-27 19:10:53 -070015#include <string>
Luis Hector Chavezf8e8f4c2017-08-01 01:09:39 -070016#include <utility>
Dylan Reid6b590e62016-10-27 19:10:53 -070017#include <vector>
18
19#include <base/json/json_reader.h>
Luis Hector Chavezf8e8f4c2017-08-01 01:09:39 -070020#include <base/strings/string_split.h>
Dylan Reid6b590e62016-10-27 19:10:53 -070021#include <base/values.h>
22
Stephen Barber5f6dc9b2017-04-04 12:36:32 -070023namespace run_oci {
Dylan Reid6b590e62016-10-27 19:10:53 -070024
25namespace {
26
yusukesfc412ea2018-01-10 08:27:24 -080027// Gets an integer from the given dictionary.
28template <typename T>
29bool ParseIntFromDict(const base::DictionaryValue& dict,
30 const char* name,
31 T* val_out) {
Dylan Reidb0a72772016-11-03 16:27:50 +000032 double double_val;
33 if (!dict.GetDouble(name, &double_val)) {
Dylan Reidb0a72772016-11-03 16:27:50 +000034 return false;
35 }
36 *val_out = double_val;
37 return true;
38}
39
Dylan Reid6b590e62016-10-27 19:10:53 -070040// Parses basic platform configuration.
41bool ParsePlatformConfig(const base::DictionaryValue& config_root_dict,
42 OciConfigPtr const& config_out) {
43 // |platform_dict| stays owned by |config_root_dict|
44 const base::DictionaryValue* platform_dict = nullptr;
45 if (!config_root_dict.GetDictionary("platform", &platform_dict)) {
46 LOG(ERROR) << "Fail to parse platform dictionary from config";
47 return false;
48 }
49
50 if (!platform_dict->GetString("os", &config_out->platform.os)) {
51 return false;
52 }
53
54 if (!platform_dict->GetString("arch", &config_out->platform.arch)) {
55 return false;
56 }
57
58 return true;
59}
60
61// Parses root fs info.
62bool ParseRootFileSystemConfig(const base::DictionaryValue& config_root_dict,
63 OciConfigPtr const& config_out) {
64 // |rootfs_dict| stays owned by |config_root_dict|
65 const base::DictionaryValue* rootfs_dict = nullptr;
66 if (!config_root_dict.GetDictionary("root", &rootfs_dict)) {
67 LOG(ERROR) << "Fail to parse rootfs dictionary from config";
68 return false;
69 }
Luis Hector Chavez2a90f292017-10-02 09:51:28 -070070 std::string path;
71 if (!rootfs_dict->GetString("path", &path)) {
Dylan Reid6b590e62016-10-27 19:10:53 -070072 LOG(ERROR) << "Fail to get rootfs path from config";
73 return false;
74 }
Luis Hector Chavez2a90f292017-10-02 09:51:28 -070075 config_out->root.path = base::FilePath(path);
Dylan Reid6d650742016-11-02 23:10:38 -070076 rootfs_dict->GetBoolean("readonly", &config_out->root.readonly);
Dylan Reid6b590e62016-10-27 19:10:53 -070077 return true;
78}
79
Luis Hector Chavez8e4dcc12017-06-27 12:54:47 -070080// Fills |config_out| with information about the capability sets in the
81// container.
82bool ParseCapabilitiesConfig(const base::DictionaryValue& capabilities_dict,
83 std::map<std::string, CapSet>* config_out) {
84 constexpr const char* kCapabilitySetNames[] = {
85 "effective", "bounding", "inheritable", "permitted", "ambient"};
86 const std::string kAmbientCapabilitySetName = "ambient";
87
88 CapSet caps_superset;
89 for (const char* set_name : kCapabilitySetNames) {
90 // |capset_list| stays owned by |capabilities_dict|.
91 const base::ListValue* capset_list = nullptr;
92 if (!capabilities_dict.GetList(set_name, &capset_list))
93 continue;
94 CapSet caps;
Luis Hector Chavez8373b412017-07-10 12:51:07 -070095 cap_value_t cap_value;
Luis Hector Chavez8e4dcc12017-06-27 12:54:47 -070096 for (const auto* cap_name_value : *capset_list) {
97 std::string cap_name;
98 if (!cap_name_value->GetAsString(&cap_name)) {
99 LOG(ERROR) << "Capability list " << set_name
100 << " contains a non-string";
101 return false;
102 }
Luis Hector Chavez8373b412017-07-10 12:51:07 -0700103 if (cap_from_name(cap_name.c_str(), &cap_value) == -1) {
Luis Hector Chavez8e4dcc12017-06-27 12:54:47 -0700104 LOG(ERROR) << "Unrecognized capability name: " << cap_name;
105 return false;
106 }
Luis Hector Chavez8373b412017-07-10 12:51:07 -0700107 caps[cap_value] = true;
Luis Hector Chavez8e4dcc12017-06-27 12:54:47 -0700108 }
109 (*config_out)[set_name] = caps;
110 caps_superset = caps;
111 }
112
113 // We currently only support sets that are identical, except that ambient is
114 // optional.
115 for (const char* set_name : kCapabilitySetNames) {
116 auto it = config_out->find(set_name);
117 if (it == config_out->end() && set_name == kAmbientCapabilitySetName) {
118 // Ambient capabilities are optional.
119 continue;
120 }
121 if (it == config_out->end()) {
122 LOG(ERROR)
123 << "If capabilities are set, all capability sets should be present";
124 return false;
125 }
126 if (it->second != caps_superset) {
127 LOG(ERROR)
128 << "If capabilities are set, all capability sets should be identical";
129 return false;
130 }
131 }
132
133 return true;
134}
135
Dylan Reid93fa4602017-06-06 13:39:31 -0700136const std::map<std::string, int> kRlimitMap = {
137#define RLIMIT_MAP_ENTRY(limit) \
138 { "RLIMIT_" #limit, RLIMIT_##limit }
Luis Hector Chavez9f9f66e2017-10-16 14:54:32 -0700139 RLIMIT_MAP_ENTRY(CPU), RLIMIT_MAP_ENTRY(FSIZE),
140 RLIMIT_MAP_ENTRY(DATA), RLIMIT_MAP_ENTRY(STACK),
141 RLIMIT_MAP_ENTRY(CORE), RLIMIT_MAP_ENTRY(RSS),
142 RLIMIT_MAP_ENTRY(NPROC), RLIMIT_MAP_ENTRY(NOFILE),
143 RLIMIT_MAP_ENTRY(MEMLOCK), RLIMIT_MAP_ENTRY(AS),
144 RLIMIT_MAP_ENTRY(LOCKS), RLIMIT_MAP_ENTRY(SIGPENDING),
145 RLIMIT_MAP_ENTRY(MSGQUEUE), RLIMIT_MAP_ENTRY(NICE),
146 RLIMIT_MAP_ENTRY(RTPRIO), RLIMIT_MAP_ENTRY(RTTIME),
Dylan Reid93fa4602017-06-06 13:39:31 -0700147#undef RLIMIT_MAP_ENTRY
148};
149
150// Fills |config_out| with information about the capability sets in the
151// container.
152bool ParseRlimitsConfig(const base::ListValue& rlimits_list,
153 std::vector<OciProcessRlimit>* rlimits_out) {
154 size_t num_limits = rlimits_list.GetSize();
155 for (size_t i = 0; i < num_limits; ++i) {
156 const base::DictionaryValue* rlimits_dict;
157 if (!rlimits_list.GetDictionary(i, &rlimits_dict)) {
158 LOG(ERROR) << "Fail to get rlimit item " << i;
159 return false;
160 }
161
162 std::string rlimit_name;
163 if (!rlimits_dict->GetString("type", &rlimit_name)) {
164 LOG(ERROR) << "Fail to get type of rlimit " << i;
165 return false;
166 }
167 const auto it = kRlimitMap.find(rlimit_name);
168 if (it == kRlimitMap.end()) {
169 LOG(ERROR) << "Unrecognized rlimit name: " << rlimit_name;
170 return false;
171 }
172
173 OciProcessRlimit limit;
174 limit.type = it->second;
yusukesfc412ea2018-01-10 08:27:24 -0800175 if (!ParseIntFromDict(*rlimits_dict, "hard", &limit.hard)) {
Dylan Reid93fa4602017-06-06 13:39:31 -0700176 LOG(ERROR) << "Fail to get hard limit of rlimit " << i;
177 return false;
178 }
yusukesfc412ea2018-01-10 08:27:24 -0800179 if (!ParseIntFromDict(*rlimits_dict, "soft", &limit.soft)) {
Dylan Reid93fa4602017-06-06 13:39:31 -0700180 LOG(ERROR) << "Fail to get soft limit of rlimit " << i;
181 return false;
182 }
183 rlimits_out->push_back(limit);
184 }
185
186 return true;
187}
188
Dylan Reid6b590e62016-10-27 19:10:53 -0700189// Fills |config_out| with information about the main process to run in the
190// container and the user it should be run as.
191bool ParseProcessConfig(const base::DictionaryValue& config_root_dict,
192 OciConfigPtr const& config_out) {
193 // |process_dict| stays owned by |config_root_dict|
194 const base::DictionaryValue* process_dict = nullptr;
195 if (!config_root_dict.GetDictionary("process", &process_dict)) {
196 LOG(ERROR) << "Fail to get main process from config";
197 return false;
198 }
199 process_dict->GetBoolean("terminal", &config_out->process.terminal);
200 // |user_dict| stays owned by |process_dict|
201 const base::DictionaryValue* user_dict = nullptr;
202 if (!process_dict->GetDictionary("user", &user_dict)) {
203 LOG(ERROR) << "Failed to get user info from config";
204 return false;
205 }
yusukesfc412ea2018-01-10 08:27:24 -0800206 if (!ParseIntFromDict(*user_dict, "uid", &config_out->process.user.uid))
Dylan Reid6b590e62016-10-27 19:10:53 -0700207 return false;
yusukesfc412ea2018-01-10 08:27:24 -0800208 if (!ParseIntFromDict(*user_dict, "gid", &config_out->process.user.gid))
Dylan Reid6b590e62016-10-27 19:10:53 -0700209 return false;
Dylan Reid6b590e62016-10-27 19:10:53 -0700210 // |args_list| stays owned by |process_dict|
211 const base::ListValue* args_list = nullptr;
212 if (!process_dict->GetList("args", &args_list)) {
213 LOG(ERROR) << "Fail to get main process args from config";
214 return false;
215 }
216 size_t num_args = args_list->GetSize();
217 for (size_t i = 0; i < num_args; ++i) {
218 std::string arg;
219 if (!args_list->GetString(i, &arg)) {
220 LOG(ERROR) << "Fail to get process args from config";
221 return false;
222 }
223 config_out->process.args.push_back(arg);
224 }
225 // |env_list| stays owned by |process_dict|
226 const base::ListValue* env_list = nullptr;
227 if (process_dict->GetList("env", &env_list)) {
228 size_t num_env = env_list->GetSize();
229 for (size_t i = 0; i < num_env; ++i) {
230 std::string env;
231 if (!env_list->GetString(i, &env)) {
232 LOG(ERROR) << "Fail to get process env from config";
233 return false;
234 }
Luis Hector Chavez855e99e2017-10-10 10:27:33 -0700235 std::vector<std::string> kvp = base::SplitString(
236 env, "=", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
237 if (kvp.size() != 2) {
238 LOG(ERROR) << "Fail to parse env \"" << env
239 << "\". Must be in name=value format.";
240 return false;
241 }
242 config_out->process.env.insert(std::make_pair(kvp[0], kvp[1]));
Dylan Reid6b590e62016-10-27 19:10:53 -0700243 }
244 }
Luis Hector Chavez855e99e2017-10-10 10:27:33 -0700245 std::string path;
246 if (!process_dict->GetString("cwd", &path)) {
Dylan Reid6b590e62016-10-27 19:10:53 -0700247 LOG(ERROR) << "failed to get cwd of process";
248 return false;
249 }
Luis Hector Chavez855e99e2017-10-10 10:27:33 -0700250 config_out->process.cwd = base::FilePath(path);
Luis Hector Chavezce1b8282017-10-30 10:12:49 -0700251 int umask_int;
252 if (process_dict->GetInteger("umask", &umask_int))
253 config_out->process.umask = static_cast<mode_t>(umask_int);
254 else
255 config_out->process.umask = 0022; // Optional
Luis Hector Chavez855e99e2017-10-10 10:27:33 -0700256
Luis Hector Chavez15e8e672017-07-20 15:13:27 -0700257 // selinuxLabel is optional.
258 process_dict->GetString("selinuxLabel", &config_out->process.selinuxLabel);
Luis Hector Chavez8e4dcc12017-06-27 12:54:47 -0700259 // |capabilities_dict| stays owned by |process_dict|
260 const base::DictionaryValue* capabilities_dict = nullptr;
261 if (process_dict->GetDictionary("capabilities", &capabilities_dict)) {
262 if (!ParseCapabilitiesConfig(*capabilities_dict,
263 &config_out->process.capabilities)) {
264 return false;
265 }
266 }
Dylan Reid6b590e62016-10-27 19:10:53 -0700267
Dylan Reid93fa4602017-06-06 13:39:31 -0700268 // |rlimit_list| stays owned by |process_dict|
269 const base::ListValue* rlimits_list = nullptr;
270 if (process_dict->GetList("rlimits", &rlimits_list)) {
271 if (!ParseRlimitsConfig(*rlimits_list, &config_out->process.rlimits)) {
272 return false;
273 }
274 }
275
Dylan Reid6b590e62016-10-27 19:10:53 -0700276 return true;
277}
278
279// Parses the 'mounts' field. The necessary mounts for running the container
280// are specified here.
281bool ParseMounts(const base::DictionaryValue& config_root_dict,
282 OciConfigPtr const& config_out) {
283 // |config_mounts_list| stays owned by |config_root_dict|
284 const base::ListValue* config_mounts_list = nullptr;
285 if (!config_root_dict.GetList("mounts", &config_mounts_list)) {
286 LOG(ERROR) << "Fail to get mounts from config dictionary";
287 return false;
288 }
289
290 for (size_t i = 0; i < config_mounts_list->GetSize(); ++i) {
291 const base::DictionaryValue* mount_dict;
292 if (!config_mounts_list->GetDictionary(i, &mount_dict)) {
293 LOG(ERROR) << "Fail to get mount item " << i;
294 return false;
295 }
296 OciMount mount;
Luis Hector Chavez855e99e2017-10-10 10:27:33 -0700297 std::string path;
298 if (!mount_dict->GetString("destination", &path)) {
Dylan Reid6b590e62016-10-27 19:10:53 -0700299 LOG(ERROR) << "Fail to get mount path for mount " << i;
300 return false;
301 }
Luis Hector Chavez855e99e2017-10-10 10:27:33 -0700302 mount.destination = base::FilePath(path);
Dylan Reid6d650742016-11-02 23:10:38 -0700303 if (!mount_dict->GetString("type", &mount.type)) {
Dylan Reid6b590e62016-10-27 19:10:53 -0700304 LOG(ERROR) << "Fail to get mount type for mount " << i;
305 return false;
306 }
Luis Hector Chavez855e99e2017-10-10 10:27:33 -0700307 if (!mount_dict->GetString("source", &path)) {
Dylan Reid6b590e62016-10-27 19:10:53 -0700308 LOG(ERROR) << "Fail to get mount source for mount " << i;
309 return false;
310 }
Luis Hector Chavez60f9bb12017-10-16 11:33:01 -0700311 if (!mount_dict->GetBoolean("performInIntermediateNamespace",
Luis Hector Chaveze2b8c5e2017-10-24 13:10:26 -0700312 &mount.performInIntermediateNamespace)) {
313 mount.performInIntermediateNamespace = false; // Optional
Luis Hector Chavez60f9bb12017-10-16 11:33:01 -0700314 }
Luis Hector Chavez855e99e2017-10-10 10:27:33 -0700315 mount.source = base::FilePath(path);
Dylan Reid6b590e62016-10-27 19:10:53 -0700316
317 // |options| are owned by |mount_dict|
318 const base::ListValue* options = nullptr;
319 if (mount_dict->GetList("options", &options)) {
320 for (size_t j = 0; j < options->GetSize(); ++j) {
321 std::string this_opt;
322 if (!options->GetString(j, &this_opt)) {
323 LOG(ERROR) << "Fail to get option " << j << " from mount options";
324 return false;
325 }
326 mount.options.push_back(this_opt);
327 }
328 }
329
330 config_out->mounts.push_back(mount);
331 }
332 return true;
333}
334
Dylan Reid6985e3b2017-03-31 19:39:16 -0700335// Parses the linux resource list
336bool ParseResources(const base::DictionaryValue& resources_dict,
337 OciLinuxResources* resources_out) {
338 // |device_list| is owned by |resources_dict|
339 const base::ListValue* device_list = nullptr;
340 if (!resources_dict.GetList("devices", &device_list)) {
341 // The device list is optional.
342 return true;
343 }
344 size_t num_devices = device_list->GetSize();
345 for (size_t i = 0; i < num_devices; ++i) {
346 OciLinuxCgroupDevice device;
347
348 const base::DictionaryValue* dev;
349 if (!device_list->GetDictionary(i, &dev)) {
350 LOG(ERROR) << "Fail to get device " << i;
351 return false;
352 }
353
354 if (!dev->GetBoolean("allow", &device.allow)) {
355 LOG(ERROR) << "Fail to get allow value for device " << i;
356 return false;
357 }
358 if (!dev->GetString("access", &device.access))
359 device.access = "rwm"; // Optional, default to all perms.
360 if (!dev->GetString("type", &device.type))
361 device.type = "a"; // Optional, default to both a means all.
yusukesfc412ea2018-01-10 08:27:24 -0800362 if (!ParseIntFromDict(*dev, "major", &device.major))
Dylan Reid6985e3b2017-03-31 19:39:16 -0700363 device.major = -1; // Optional, -1 will map to all devices.
yusukesfc412ea2018-01-10 08:27:24 -0800364 if (!ParseIntFromDict(*dev, "minor", &device.minor))
Dylan Reid6985e3b2017-03-31 19:39:16 -0700365 device.minor = -1; // Optional, -1 will map to all devices.
366
367 resources_out->devices.push_back(device);
368 }
369
370 return true;
371}
372
Stephen Barber3c0a2022017-09-08 14:17:57 -0700373// Parses the list of namespaces and fills |namespaces_out| with them.
374bool ParseNamespaces(const base::ListValue* namespaces_list,
375 std::vector<OciNamespace>* namespaces_out) {
376 for (size_t i = 0; i < namespaces_list->GetSize(); ++i) {
377 OciNamespace new_namespace;
378 const base::DictionaryValue* ns;
379 if (!namespaces_list->GetDictionary(i, &ns)) {
380 LOG(ERROR) << "Failed to get namespace " << i;
381 return false;
382 }
383 if (!ns->GetString("type", &new_namespace.type)) {
384 LOG(ERROR) << "Namespace " << i << " missing type";
385 return false;
386 }
Luis Hector Chavez855e99e2017-10-10 10:27:33 -0700387 std::string path;
388 if (ns->GetString("path", &path))
389 new_namespace.path = base::FilePath(path);
Stephen Barber3c0a2022017-09-08 14:17:57 -0700390 namespaces_out->push_back(new_namespace);
391 }
392 return true;
393}
394
Dylan Reid6b590e62016-10-27 19:10:53 -0700395// Parse the list of device nodes that the container needs to run.
396bool ParseDeviceList(const base::DictionaryValue& linux_dict,
397 OciConfigPtr const& config_out) {
398 // |device_list| is owned by |linux_dict|
399 const base::ListValue* device_list = nullptr;
400 if (!linux_dict.GetList("devices", &device_list)) {
Dylan Reida5ed1272016-11-11 16:43:39 -0800401 // The device list is optional.
402 return true;
Dylan Reid6b590e62016-10-27 19:10:53 -0700403 }
404 size_t num_devices = device_list->GetSize();
405 for (size_t i = 0; i < num_devices; ++i) {
406 OciLinuxDevice device;
407
408 const base::DictionaryValue* dev;
409 if (!device_list->GetDictionary(i, &dev)) {
410 LOG(ERROR) << "Fail to get device " << i;
411 return false;
412 }
413 std::string path;
Luis Hector Chavez855e99e2017-10-10 10:27:33 -0700414 if (!dev->GetString("path", &path)) {
Dylan Reid6b590e62016-10-27 19:10:53 -0700415 LOG(ERROR) << "Fail to get path for dev";
416 return false;
417 }
Luis Hector Chavez855e99e2017-10-10 10:27:33 -0700418 device.path = base::FilePath(path);
Dylan Reid6d650742016-11-02 23:10:38 -0700419 if (!dev->GetString("type", &device.type)) {
Luis Hector Chavez855e99e2017-10-10 10:27:33 -0700420 LOG(ERROR) << "Fail to get type for " << device.path.value();
Dylan Reid6b590e62016-10-27 19:10:53 -0700421 return false;
422 }
Stephen Barber7bae6642017-11-30 10:47:12 -0800423 dev->GetBoolean("dynamicMajor", &device.dynamicMajor);
424 if (device.dynamicMajor) {
425 if (dev->HasKey("major")) {
426 LOG(WARNING)
427 << "Ignoring \"major\" since \"dynamicMajor\" is specified for "
428 << device.path.value();
429 }
430 } else {
yusukesfc412ea2018-01-10 08:27:24 -0800431 if (!ParseIntFromDict(*dev, "major", &device.major))
Stephen Barber7bae6642017-11-30 10:47:12 -0800432 return false;
433 }
434
Luis Hector Chavezac20fc52017-10-10 11:08:41 -0700435 dev->GetBoolean("dynamicMinor", &device.dynamicMinor);
436 if (device.dynamicMinor) {
437 if (dev->HasKey("minor")) {
438 LOG(WARNING)
439 << "Ignoring \"minor\" since \"dynamicMinor\" is specified for "
440 << device.path.value();
441 }
442 } else {
yusukesfc412ea2018-01-10 08:27:24 -0800443 if (!ParseIntFromDict(*dev, "minor", &device.minor))
Luis Hector Chavezac20fc52017-10-10 11:08:41 -0700444 return false;
445 }
yusukesfc412ea2018-01-10 08:27:24 -0800446 if (!ParseIntFromDict(*dev, "fileMode", &device.fileMode))
Dylan Reidc0c28502016-11-04 10:51:30 -0700447 return false;
yusukesfc412ea2018-01-10 08:27:24 -0800448 if (!ParseIntFromDict(*dev, "uid", &device.uid))
Dylan Reidc0c28502016-11-04 10:51:30 -0700449 return false;
yusukesfc412ea2018-01-10 08:27:24 -0800450 if (!ParseIntFromDict(*dev, "gid", &device.gid))
Dylan Reidc0c28502016-11-04 10:51:30 -0700451 return false;
Dylan Reid6b590e62016-10-27 19:10:53 -0700452
453 config_out->linux_config.devices.push_back(device);
454 }
455
456 return true;
457}
458
459// Parses the list of ID mappings and fills |mappings_out| with them.
460bool ParseLinuxIdMappings(const base::ListValue* id_map_list,
Ben Chanf43980b2017-03-10 11:31:46 -0800461 std::vector<OciLinuxNamespaceMapping>* mappings_out) {
Dylan Reid6b590e62016-10-27 19:10:53 -0700462 for (size_t i = 0; i < id_map_list->GetSize(); ++i) {
463 OciLinuxNamespaceMapping new_map;
464 const base::DictionaryValue* map;
465 if (!id_map_list->GetDictionary(i, &map)) {
466 LOG(ERROR) << "Fail to get id map " << i;
467 return false;
468 }
yusukesfc412ea2018-01-10 08:27:24 -0800469 if (!ParseIntFromDict(*map, "hostID", &new_map.hostID))
Dylan Reid6b590e62016-10-27 19:10:53 -0700470 return false;
yusukesfc412ea2018-01-10 08:27:24 -0800471 if (!ParseIntFromDict(*map, "containerID", &new_map.containerID))
Dylan Reid6b590e62016-10-27 19:10:53 -0700472 return false;
yusukesfc412ea2018-01-10 08:27:24 -0800473 if (!ParseIntFromDict(*map, "size", &new_map.size))
Dylan Reid6b590e62016-10-27 19:10:53 -0700474 return false;
Ben Chanf43980b2017-03-10 11:31:46 -0800475 mappings_out->push_back(new_map);
Dylan Reid6b590e62016-10-27 19:10:53 -0700476 }
477 return true;
478}
479
Dylan Reidb0a72772016-11-03 16:27:50 +0000480// Parses seccomp syscall args.
481bool ParseSeccompArgs(const base::DictionaryValue& syscall_dict,
482 OciSeccompSyscall* syscall_out) {
483 const base::ListValue* args = nullptr;
484 if (syscall_dict.GetList("args", &args)) {
485 for (size_t i = 0; i < args->GetSize(); ++i) {
486 const base::DictionaryValue* args_dict = nullptr;
487 if (!args->GetDictionary(i, &args_dict)) {
488 LOG(ERROR) << "Failed to pars args dict for " << syscall_out->name;
489 return false;
490 }
491 OciSeccompArg this_arg;
yusukesfc412ea2018-01-10 08:27:24 -0800492 if (!ParseIntFromDict(*args_dict, "index", &this_arg.index))
Dylan Reidb0a72772016-11-03 16:27:50 +0000493 return false;
yusukesfc412ea2018-01-10 08:27:24 -0800494 if (!ParseIntFromDict(*args_dict, "value", &this_arg.value))
Dylan Reidb0a72772016-11-03 16:27:50 +0000495 return false;
yusukesfc412ea2018-01-10 08:27:24 -0800496 if (!ParseIntFromDict(*args_dict, "value2", &this_arg.value2))
Dylan Reidb0a72772016-11-03 16:27:50 +0000497 return false;
498 if (!args_dict->GetString("op", &this_arg.op)) {
Luis Hector Chavez9f9f66e2017-10-16 14:54:32 -0700499 LOG(ERROR) << "Failed to parse op for arg " << this_arg.index << " of "
500 << syscall_out->name;
Dylan Reidb0a72772016-11-03 16:27:50 +0000501 return false;
502 }
503 syscall_out->args.push_back(this_arg);
504 }
505 }
506 return true;
507}
508
509// Parses the seccomp node if it is present.
510bool ParseSeccompInfo(const base::DictionaryValue& seccomp_dict,
511 OciSeccomp* seccomp_out) {
Luis Hector Chavez9f9f66e2017-10-16 14:54:32 -0700512 if (!seccomp_dict.GetString("defaultAction", &seccomp_out->defaultAction))
Dylan Reidb0a72772016-11-03 16:27:50 +0000513 return false;
514
515 // Gets the list of architectures.
516 const base::ListValue* architectures = nullptr;
517 if (!seccomp_dict.GetList("architectures", &architectures)) {
518 LOG(ERROR) << "Fail to read seccomp architectures";
519 return false;
520 }
521 for (size_t i = 0; i < architectures->GetSize(); ++i) {
522 std::string this_arch;
523 if (!architectures->GetString(i, &this_arch)) {
524 LOG(ERROR) << "Fail to parse seccomp architecture list";
525 return false;
526 }
527 seccomp_out->architectures.push_back(this_arch);
528 }
529
530 // Gets the list of syscalls.
531 const base::ListValue* syscalls = nullptr;
532 if (!seccomp_dict.GetList("syscalls", &syscalls)) {
533 LOG(ERROR) << "Fail to read seccomp syscalls";
534 return false;
535 }
536 for (size_t i = 0; i < syscalls->GetSize(); ++i) {
537 const base::DictionaryValue* syscall_dict = nullptr;
538 if (!syscalls->GetDictionary(i, &syscall_dict)) {
539 LOG(ERROR) << "Fail to parse seccomp syscalls list";
540 return false;
541 }
542 OciSeccompSyscall this_syscall;
543 if (!syscall_dict->GetString("name", &this_syscall.name)) {
544 LOG(ERROR) << "Fail to parse syscall name " << i;
545 return false;
546 }
547 if (!syscall_dict->GetString("action", &this_syscall.action)) {
548 LOG(ERROR) << "Fail to parse syscall action for " << this_syscall.name;
549 return false;
550 }
551 if (!ParseSeccompArgs(*syscall_dict, &this_syscall))
552 return false;
553 seccomp_out->syscalls.push_back(this_syscall);
554 }
555
556 return true;
557}
558
Luis Hector Chavez7c6fddf2017-10-24 15:39:41 -0700559constexpr std::pair<const char*, int> kMountPropagationMapping[] = {
560 {"rprivate", MS_PRIVATE | MS_REC}, {"private", MS_PRIVATE},
561 {"rslave", MS_SLAVE | MS_REC}, {"slave", MS_SLAVE},
562 {"rshared", MS_SHARED | MS_REC}, {"shared", MS_SHARED},
563 {"", MS_SLAVE | MS_REC}, // Default value.
564};
565
566bool ParseMountPropagationFlags(const std::string& propagation,
567 int* propagation_flags_out) {
568 for (const auto& entry : kMountPropagationMapping) {
569 if (propagation == entry.first) {
570 *propagation_flags_out = entry.second;
571 return true;
572 }
573 }
574 LOG(ERROR) << "Unrecognized mount propagation flags: " << propagation;
575 return false;
576}
577
Luis Hector Chavez6e9a5332017-10-26 14:41:49 -0700578constexpr std::pair<const char*, uint64_t> kSecurebitsMapping[] = {
579#define SECUREBIT_MAP_ENTRY(secbit) \
580 { #secbit, SECBIT_##secbit }
581 SECUREBIT_MAP_ENTRY(NOROOT), SECUREBIT_MAP_ENTRY(NOROOT_LOCKED),
582 SECUREBIT_MAP_ENTRY(NO_SETUID_FIXUP),
583 SECUREBIT_MAP_ENTRY(NO_SETUID_FIXUP_LOCKED), SECUREBIT_MAP_ENTRY(KEEP_CAPS),
584 SECUREBIT_MAP_ENTRY(KEEP_CAPS_LOCKED),
585#if defined(SECBIT_NO_CAP_AMBIENT_RAISE)
586 // Kernels < v4.4 do not have this.
587 SECUREBIT_MAP_ENTRY(NO_CAP_AMBIENT_RAISE),
588 SECUREBIT_MAP_ENTRY(NO_CAP_AMBIENT_RAISE_LOCKED),
589#endif // SECBIT_NO_CAP_AMBIENT_RAISE
590#undef SECUREBIT_MAP_ENTRY
591};
592
593bool ParseSecurebit(const std::string& securebit_name, uint64_t* mask_out) {
594 for (const auto& entry : kSecurebitsMapping) {
595 if (securebit_name == entry.first) {
596 *mask_out = entry.second;
597 return true;
598 }
599 }
600 LOG(ERROR) << "Unrecognized securebit name: " << securebit_name;
601 return false;
602}
603
604bool ParseSkipSecurebitsMask(const base::ListValue& skip_securebits_list,
605 uint64_t* securebits_mask_out) {
606 size_t num_securebits = skip_securebits_list.GetSize();
607 for (size_t i = 0; i < num_securebits; ++i) {
608 std::string securebit_name;
609 if (!skip_securebits_list.GetString(i, &securebit_name)) {
610 LOG(ERROR) << "Fail to get securebit name " << i;
611 return false;
612 }
613 uint64_t mask = 0;
614 if (!ParseSecurebit(securebit_name, &mask))
615 return false;
616 *securebits_mask_out |= mask;
617 }
618 return true;
619}
620
Dylan Reid6b590e62016-10-27 19:10:53 -0700621// Parses the linux node which has information about setting up a user
622// namespace, and the list of devices for the container.
623bool ParseLinuxConfigDict(const base::DictionaryValue& runtime_root_dict,
624 OciConfigPtr const& config_out) {
625 // |linux_dict| is owned by |runtime_root_dict|
626 const base::DictionaryValue* linux_dict = nullptr;
627 if (!runtime_root_dict.GetDictionary("linux", &linux_dict)) {
628 LOG(ERROR) << "Fail to get linux dictionary from the runtime dictionary";
629 return false;
630 }
631
632 // |uid_map_list| is owned by |linux_dict|
633 const base::ListValue* uid_map_list = nullptr;
Stephen Barber771653f2017-10-04 23:48:57 -0700634 if (linux_dict->GetList("uidMappings", &uid_map_list))
635 ParseLinuxIdMappings(uid_map_list, &config_out->linux_config.uidMappings);
Dylan Reid6b590e62016-10-27 19:10:53 -0700636
637 // |gid_map_list| is owned by |linux_dict|
638 const base::ListValue* gid_map_list = nullptr;
Stephen Barber771653f2017-10-04 23:48:57 -0700639 if (linux_dict->GetList("gidMappings", &gid_map_list))
640 ParseLinuxIdMappings(gid_map_list, &config_out->linux_config.gidMappings);
Dylan Reid6b590e62016-10-27 19:10:53 -0700641
642 if (!ParseDeviceList(*linux_dict, config_out))
643 return false;
644
Dylan Reid6985e3b2017-03-31 19:39:16 -0700645 const base::DictionaryValue* resources_dict = nullptr;
646 if (linux_dict->GetDictionary("resources", &resources_dict)) {
647 if (!ParseResources(*resources_dict, &config_out->linux_config.resources))
648 return false;
649 }
650
Stephen Barber3c0a2022017-09-08 14:17:57 -0700651 const base::ListValue* namespaces_list = nullptr;
652 if (linux_dict->GetList("namespaces", &namespaces_list)) {
653 if (!ParseNamespaces(namespaces_list, &config_out->linux_config.namespaces))
654 return false;
655 }
656
Dylan Reidb0a72772016-11-03 16:27:50 +0000657 const base::DictionaryValue* seccomp_dict = nullptr;
658 if (linux_dict->GetDictionary("seccomp", &seccomp_dict)) {
659 if (!ParseSeccompInfo(*seccomp_dict, &config_out->linux_config.seccomp))
660 return false;
661 }
662
Luis Hector Chavez7c6fddf2017-10-24 15:39:41 -0700663 std::string rootfs_propagation_string;
664 if (!linux_dict->GetString("rootfsPropagation", &rootfs_propagation_string))
665 rootfs_propagation_string = std::string(); // Optional
666 if (!ParseMountPropagationFlags(
667 rootfs_propagation_string,
668 &config_out->linux_config.rootfsPropagation)) {
669 return false;
670 }
671
Luis Hector Chavez45ac1242017-10-26 13:21:16 -0700672 std::string cgroups_path_string;
673 if (linux_dict->GetString("cgroupsPath", &cgroups_path_string))
674 config_out->linux_config.cgroupsPath = base::FilePath(cgroups_path_string);
675
Luis Hector Chavez0f3d7a42017-10-26 10:48:30 -0700676 if (!linux_dict->GetString("altSyscall",
677 &config_out->linux_config.altSyscall)) {
678 config_out->linux_config.altSyscall = std::string(); // Optional
679 }
680
Luis Hector Chavez6e9a5332017-10-26 14:41:49 -0700681 const base::ListValue* skip_securebits_list;
682 if (linux_dict->GetList("skipSecurebits", &skip_securebits_list)) {
683 if (!ParseSkipSecurebitsMask(*skip_securebits_list,
684 &config_out->linux_config.skipSecurebits)) {
685 return false;
686 }
687 } else {
688 config_out->linux_config.skipSecurebits = 0; // Optional
689 }
690
Dylan Reid6b590e62016-10-27 19:10:53 -0700691 return true;
692}
693
Dylan Reid45e34fe2016-12-02 15:11:53 -0800694bool HostnameValid(const std::string& hostname) {
695 if (hostname.length() > 255)
696 return false;
697
698 const std::regex name("^[0-9a-zA-Z]([0-9a-zA-Z-]*[0-9a-zA-Z])?$");
699 if (!std::regex_match(hostname, name))
700 return false;
701
702 const std::regex double_dash("--");
703 if (std::regex_match(hostname, double_dash))
704 return false;
705
706 return true;
707}
708
Luis Hector Chavezf8e8f4c2017-08-01 01:09:39 -0700709bool ParseHooksList(const base::ListValue& hooks_list,
710 std::vector<OciHook>* hooks_out,
711 const std::string& hook_type) {
712 size_t num_hooks = hooks_list.GetSize();
713 for (size_t i = 0; i < num_hooks; ++i) {
714 OciHook hook;
715 const base::DictionaryValue* hook_dict;
716 if (!hooks_list.GetDictionary(i, &hook_dict)) {
717 LOG(ERROR) << "Fail to get " << hook_type << " hook item " << i;
718 return false;
719 }
720
Luis Hector Chavez855e99e2017-10-10 10:27:33 -0700721 std::string path;
722 if (!hook_dict->GetString("path", &path)) {
Luis Hector Chavezf8e8f4c2017-08-01 01:09:39 -0700723 LOG(ERROR) << "Fail to get path of " << hook_type << " hook " << i;
724 return false;
725 }
Luis Hector Chavez855e99e2017-10-10 10:27:33 -0700726 hook.path = base::FilePath(path);
Luis Hector Chavezf8e8f4c2017-08-01 01:09:39 -0700727
728 const base::ListValue* hook_args;
729 // args are optional.
730 if (hook_dict->GetList("args", &hook_args)) {
731 size_t num_args = hook_args->GetSize();
732 for (size_t j = 0; j < num_args; ++j) {
733 std::string arg;
734 if (!hook_args->GetString(j, &arg)) {
735 LOG(ERROR) << "Fail to get arg " << j << " of " << hook_type
736 << " hook " << i;
737 return false;
738 }
739 hook.args.push_back(arg);
740 }
741 }
742
743 const base::ListValue* hook_envs;
744 // envs are optional.
745 if (hook_dict->GetList("env", &hook_envs)) {
746 size_t num_env = hook_envs->GetSize();
747 for (size_t j = 0; j < num_env; ++j) {
748 std::string env;
749 if (!hook_envs->GetString(j, &env)) {
750 LOG(ERROR) << "Fail to get env " << j << " of " << hook_type
751 << " hook " << i;
752 return false;
753 }
754 std::vector<std::string> kvp = base::SplitString(
755 env, "=", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
756 if (kvp.size() != 2) {
757 LOG(ERROR) << "Fail to parse env \"" << env
758 << "\". Must be in name=value format.";
759 return false;
760 }
761 hook.env.insert(std::make_pair(kvp[0], kvp[1]));
762 }
763 }
764
765 int timeout_seconds;
766 // timeout is optional.
767 if (hook_dict->GetInteger("timeout", &timeout_seconds)) {
768 hook.timeout = base::TimeDelta::FromSeconds(timeout_seconds);
769 } else {
770 hook.timeout = base::TimeDelta::Max();
771 }
772
773 hooks_out->emplace_back(std::move(hook));
774 }
775 return true;
776}
777
778bool ParseHooks(const base::DictionaryValue& config_root_dict,
779 OciConfigPtr const& config_out) {
780 const base::DictionaryValue* hooks_config_dict;
781 if (!config_root_dict.GetDictionary("hooks", &hooks_config_dict)) {
782 // Hooks are optional.
783 return true;
784 }
785
786 const base::ListValue* hooks_list;
Luis Hector Chavezbb515a02017-09-29 15:44:35 -0700787 if (hooks_config_dict->GetList("prechroot", &hooks_list)) {
788 if (!ParseHooksList(*hooks_list, &config_out->pre_chroot_hooks,
789 "prechroot")) {
790 return false;
791 }
792 }
Luis Hector Chavezf8e8f4c2017-08-01 01:09:39 -0700793 if (hooks_config_dict->GetList("prestart", &hooks_list)) {
794 if (!ParseHooksList(*hooks_list, &config_out->pre_start_hooks, "prestart"))
795 return false;
796 }
797 if (hooks_config_dict->GetList("poststart", &hooks_list)) {
Luis Hector Chavez9f9f66e2017-10-16 14:54:32 -0700798 if (!ParseHooksList(*hooks_list, &config_out->post_start_hooks,
799 "poststart"))
Luis Hector Chavezf8e8f4c2017-08-01 01:09:39 -0700800 return false;
801 }
802 if (hooks_config_dict->GetList("poststop", &hooks_list)) {
803 if (!ParseHooksList(*hooks_list, &config_out->post_stop_hooks, "poststop"))
804 return false;
805 }
806 return true;
807}
808
Dylan Reid6b590e62016-10-27 19:10:53 -0700809// Parses the configuration file for the container. The config file specifies
810// basic filesystem info and details about the process to be run. namespace,
811// cgroup, and syscall configurations are also specified
812bool ParseConfigDict(const base::DictionaryValue& config_root_dict,
813 OciConfigPtr const& config_out) {
Dylan Reid6d650742016-11-02 23:10:38 -0700814 if (!config_root_dict.GetString("ociVersion", &config_out->ociVersion)) {
Dylan Reid6b590e62016-10-27 19:10:53 -0700815 LOG(ERROR) << "Failed to parse ociVersion";
816 return false;
817 }
818 if (!config_root_dict.GetString("hostname", &config_out->hostname)) {
819 LOG(ERROR) << "Failed to parse hostname";
820 return false;
821 }
Dylan Reid45e34fe2016-12-02 15:11:53 -0800822 if (!HostnameValid(config_out->hostname)) {
823 LOG(ERROR) << "Invalid hostname " << config_out->hostname;
824 return false;
825 }
Dylan Reid6b590e62016-10-27 19:10:53 -0700826
827 // Platform info
828 if (!ParsePlatformConfig(config_root_dict, config_out)) {
829 return false;
830 }
831
832 // Root fs info
833 if (!ParseRootFileSystemConfig(config_root_dict, config_out)) {
834 return false;
835 }
836
837 // Process info
838 if (!ParseProcessConfig(config_root_dict, config_out)) {
839 return false;
840 }
841
842 // Get a list of mount points and mounts.
843 if (!ParseMounts(config_root_dict, config_out)) {
844 LOG(ERROR) << "Failed to parse mounts";
845 return false;
846 }
847
Luis Hector Chavezf8e8f4c2017-08-01 01:09:39 -0700848 // Hooks info
849 if (!ParseHooks(config_root_dict, config_out)) {
850 return false;
851 }
852
Dylan Reid6b590e62016-10-27 19:10:53 -0700853 // Parse linux node.
854 if (!ParseLinuxConfigDict(config_root_dict, config_out)) {
855 LOG(ERROR) << "Failed to parse the linux node";
856 return false;
857 }
858
859 return true;
860}
861
Ben Chanf43980b2017-03-10 11:31:46 -0800862} // anonymous namespace
Dylan Reid6b590e62016-10-27 19:10:53 -0700863
864bool ParseContainerConfig(const std::string& config_json_data,
865 OciConfigPtr const& config_out) {
Luis Hector Chavezd63f1ba2017-06-28 15:58:12 -0700866 std::string error_msg;
Dylan Reid6b590e62016-10-27 19:10:53 -0700867 std::unique_ptr<const base::Value> config_root_val =
Luis Hector Chavez9f9f66e2017-10-16 14:54:32 -0700868 base::JSONReader::ReadAndReturnError(
869 config_json_data, base::JSON_PARSE_RFC, nullptr /* error_code_out */,
870 &error_msg, nullptr /* error_line_out */,
871 nullptr /* error_column_out */);
Dylan Reid6b590e62016-10-27 19:10:53 -0700872 if (!config_root_val) {
Luis Hector Chavezd63f1ba2017-06-28 15:58:12 -0700873 LOG(ERROR) << "Fail to parse config.json: " << error_msg;
Dylan Reid6b590e62016-10-27 19:10:53 -0700874 return false;
875 }
876 const base::DictionaryValue* config_dict = nullptr;
877 if (!config_root_val->GetAsDictionary(&config_dict)) {
878 LOG(ERROR) << "Fail to parse root dictionary from config.json";
879 return false;
880 }
881 if (!ParseConfigDict(*config_dict, config_out)) {
882 return false;
883 }
884
885 return true;
886}
887
Stephen Barber5f6dc9b2017-04-04 12:36:32 -0700888} // namespace run_oci