blob: ce82741eddb734704fadacca020b2bbf65c065fd [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
5#include "container_utils/container_config_parser.h"
6
7#include <unistd.h>
8
Ben Chanf43980b2017-03-10 11:31:46 -08009#include <regex> // NOLINT(build/c++11)
Dylan Reid6b590e62016-10-27 19:10:53 -070010#include <string>
11#include <vector>
12
13#include <base/json/json_reader.h>
14#include <base/values.h>
15
16namespace container_utils {
17
18namespace {
19
Dylan Reidc0c28502016-11-04 10:51:30 -070020// Gets a uint32 from the given dictionary.
21bool ParseUint32FromDict(const base::DictionaryValue& dict, const char *name,
22 uint32_t* val_out) {
23 double double_val;
24 if (!dict.GetDouble(name, &double_val)) {
25 LOG(ERROR) << "Failed to get " << name << " uint32_t value from config";
26 return false;
27 }
28 *val_out = double_val;
29 return true;
30}
31
Dylan Reidb0a72772016-11-03 16:27:50 +000032// Gets a uint64 from the given dictionary.
33bool ParseUint64FromDict(const base::DictionaryValue& dict, const char *name,
34 uint64_t* val_out) {
35 double double_val;
36 if (!dict.GetDouble(name, &double_val)) {
37 LOG(ERROR) << "Failed to get " << name << " uid info from config";
38 return false;
39 }
40 *val_out = double_val;
41 return true;
42}
43
Dylan Reid6b590e62016-10-27 19:10:53 -070044// Parses basic platform configuration.
45bool ParsePlatformConfig(const base::DictionaryValue& config_root_dict,
46 OciConfigPtr const& config_out) {
47 // |platform_dict| stays owned by |config_root_dict|
48 const base::DictionaryValue* platform_dict = nullptr;
49 if (!config_root_dict.GetDictionary("platform", &platform_dict)) {
50 LOG(ERROR) << "Fail to parse platform dictionary from config";
51 return false;
52 }
53
54 if (!platform_dict->GetString("os", &config_out->platform.os)) {
55 return false;
56 }
57
58 if (!platform_dict->GetString("arch", &config_out->platform.arch)) {
59 return false;
60 }
61
62 return true;
63}
64
65// Parses root fs info.
66bool ParseRootFileSystemConfig(const base::DictionaryValue& config_root_dict,
67 OciConfigPtr const& config_out) {
68 // |rootfs_dict| stays owned by |config_root_dict|
69 const base::DictionaryValue* rootfs_dict = nullptr;
70 if (!config_root_dict.GetDictionary("root", &rootfs_dict)) {
71 LOG(ERROR) << "Fail to parse rootfs dictionary from config";
72 return false;
73 }
74 if (!rootfs_dict->GetString("path", &config_out->root.path)) {
75 LOG(ERROR) << "Fail to get rootfs path from config";
76 return false;
77 }
Dylan Reid6d650742016-11-02 23:10:38 -070078 rootfs_dict->GetBoolean("readonly", &config_out->root.readonly);
Dylan Reid6b590e62016-10-27 19:10:53 -070079 return true;
80}
81
82// Fills |config_out| with information about the main process to run in the
83// container and the user it should be run as.
84bool ParseProcessConfig(const base::DictionaryValue& config_root_dict,
85 OciConfigPtr const& config_out) {
86 // |process_dict| stays owned by |config_root_dict|
87 const base::DictionaryValue* process_dict = nullptr;
88 if (!config_root_dict.GetDictionary("process", &process_dict)) {
89 LOG(ERROR) << "Fail to get main process from config";
90 return false;
91 }
92 process_dict->GetBoolean("terminal", &config_out->process.terminal);
93 // |user_dict| stays owned by |process_dict|
94 const base::DictionaryValue* user_dict = nullptr;
95 if (!process_dict->GetDictionary("user", &user_dict)) {
96 LOG(ERROR) << "Failed to get user info from config";
97 return false;
98 }
Dylan Reidc0c28502016-11-04 10:51:30 -070099 if (!ParseUint32FromDict(*user_dict, "uid", &config_out->process.user.uid))
Dylan Reid6b590e62016-10-27 19:10:53 -0700100 return false;
Dylan Reidc0c28502016-11-04 10:51:30 -0700101 if (!ParseUint32FromDict(*user_dict, "gid", &config_out->process.user.gid))
Dylan Reid6b590e62016-10-27 19:10:53 -0700102 return false;
Dylan Reid6b590e62016-10-27 19:10:53 -0700103 // |args_list| stays owned by |process_dict|
104 const base::ListValue* args_list = nullptr;
105 if (!process_dict->GetList("args", &args_list)) {
106 LOG(ERROR) << "Fail to get main process args from config";
107 return false;
108 }
109 size_t num_args = args_list->GetSize();
110 for (size_t i = 0; i < num_args; ++i) {
111 std::string arg;
112 if (!args_list->GetString(i, &arg)) {
113 LOG(ERROR) << "Fail to get process args from config";
114 return false;
115 }
116 config_out->process.args.push_back(arg);
117 }
118 // |env_list| stays owned by |process_dict|
119 const base::ListValue* env_list = nullptr;
120 if (process_dict->GetList("env", &env_list)) {
121 size_t num_env = env_list->GetSize();
122 for (size_t i = 0; i < num_env; ++i) {
123 std::string env;
124 if (!env_list->GetString(i, &env)) {
125 LOG(ERROR) << "Fail to get process env from config";
126 return false;
127 }
128 config_out->process.env.push_back(env);
129 }
130 }
131 if (!process_dict->GetString("cwd", &config_out->process.cwd)) {
132 LOG(ERROR) << "failed to get cwd of process";
133 return false;
134 }
135
136 return true;
137}
138
139// Parses the 'mounts' field. The necessary mounts for running the container
140// are specified here.
141bool ParseMounts(const base::DictionaryValue& config_root_dict,
142 OciConfigPtr const& config_out) {
143 // |config_mounts_list| stays owned by |config_root_dict|
144 const base::ListValue* config_mounts_list = nullptr;
145 if (!config_root_dict.GetList("mounts", &config_mounts_list)) {
146 LOG(ERROR) << "Fail to get mounts from config dictionary";
147 return false;
148 }
149
150 for (size_t i = 0; i < config_mounts_list->GetSize(); ++i) {
151 const base::DictionaryValue* mount_dict;
152 if (!config_mounts_list->GetDictionary(i, &mount_dict)) {
153 LOG(ERROR) << "Fail to get mount item " << i;
154 return false;
155 }
156 OciMount mount;
157 if (!mount_dict->GetString("destination", &mount.destination)) {
158 LOG(ERROR) << "Fail to get mount path for mount " << i;
159 return false;
160 }
Dylan Reid6d650742016-11-02 23:10:38 -0700161 if (!mount_dict->GetString("type", &mount.type)) {
Dylan Reid6b590e62016-10-27 19:10:53 -0700162 LOG(ERROR) << "Fail to get mount type for mount " << i;
163 return false;
164 }
165 if (!mount_dict->GetString("source", &mount.source)) {
166 LOG(ERROR) << "Fail to get mount source for mount " << i;
167 return false;
168 }
169
170 // |options| are owned by |mount_dict|
171 const base::ListValue* options = nullptr;
172 if (mount_dict->GetList("options", &options)) {
173 for (size_t j = 0; j < options->GetSize(); ++j) {
174 std::string this_opt;
175 if (!options->GetString(j, &this_opt)) {
176 LOG(ERROR) << "Fail to get option " << j << " from mount options";
177 return false;
178 }
179 mount.options.push_back(this_opt);
180 }
181 }
182
183 config_out->mounts.push_back(mount);
184 }
185 return true;
186}
187
188// Parse the list of device nodes that the container needs to run.
189bool ParseDeviceList(const base::DictionaryValue& linux_dict,
190 OciConfigPtr const& config_out) {
191 // |device_list| is owned by |linux_dict|
192 const base::ListValue* device_list = nullptr;
193 if (!linux_dict.GetList("devices", &device_list)) {
Dylan Reida5ed1272016-11-11 16:43:39 -0800194 // The device list is optional.
195 return true;
Dylan Reid6b590e62016-10-27 19:10:53 -0700196 }
197 size_t num_devices = device_list->GetSize();
198 for (size_t i = 0; i < num_devices; ++i) {
199 OciLinuxDevice device;
200
201 const base::DictionaryValue* dev;
202 if (!device_list->GetDictionary(i, &dev)) {
203 LOG(ERROR) << "Fail to get device " << i;
204 return false;
205 }
206 std::string path;
207 if (!dev->GetString("path", &device.path)) {
208 LOG(ERROR) << "Fail to get path for dev";
209 return false;
210 }
Dylan Reid6d650742016-11-02 23:10:38 -0700211 if (!dev->GetString("type", &device.type)) {
Dylan Reid6b590e62016-10-27 19:10:53 -0700212 LOG(ERROR) << "Fail to get type for " << device.path;
213 return false;
214 }
Dylan Reidc0c28502016-11-04 10:51:30 -0700215 if (!ParseUint32FromDict(*dev, "major", &device.major))
216 return false;
217 if (!ParseUint32FromDict(*dev, "minor", &device.minor))
218 return false;
219 if (!ParseUint32FromDict(*dev, "fileMode", &device.fileMode))
220 return false;
221 if (!ParseUint32FromDict(*dev, "uid", &device.uid))
222 return false;
223 if (!ParseUint32FromDict(*dev, "gid", &device.gid))
224 return false;
Dylan Reid6b590e62016-10-27 19:10:53 -0700225
226 config_out->linux_config.devices.push_back(device);
227 }
228
229 return true;
230}
231
232// Parses the list of ID mappings and fills |mappings_out| with them.
233bool ParseLinuxIdMappings(const base::ListValue* id_map_list,
Ben Chanf43980b2017-03-10 11:31:46 -0800234 std::vector<OciLinuxNamespaceMapping>* mappings_out) {
Dylan Reid6b590e62016-10-27 19:10:53 -0700235 for (size_t i = 0; i < id_map_list->GetSize(); ++i) {
236 OciLinuxNamespaceMapping new_map;
237 const base::DictionaryValue* map;
238 if (!id_map_list->GetDictionary(i, &map)) {
239 LOG(ERROR) << "Fail to get id map " << i;
240 return false;
241 }
Dylan Reidc0c28502016-11-04 10:51:30 -0700242 if (!ParseUint32FromDict(*map, "hostID", &new_map.hostID))
Dylan Reid6b590e62016-10-27 19:10:53 -0700243 return false;
Dylan Reidc0c28502016-11-04 10:51:30 -0700244 if (!ParseUint32FromDict(*map, "containerID", &new_map.containerID))
Dylan Reid6b590e62016-10-27 19:10:53 -0700245 return false;
Dylan Reidc0c28502016-11-04 10:51:30 -0700246 if (!ParseUint32FromDict(*map, "size", &new_map.size))
Dylan Reid6b590e62016-10-27 19:10:53 -0700247 return false;
Ben Chanf43980b2017-03-10 11:31:46 -0800248 mappings_out->push_back(new_map);
Dylan Reid6b590e62016-10-27 19:10:53 -0700249 }
250 return true;
251}
252
Dylan Reidb0a72772016-11-03 16:27:50 +0000253// Parses seccomp syscall args.
254bool ParseSeccompArgs(const base::DictionaryValue& syscall_dict,
255 OciSeccompSyscall* syscall_out) {
256 const base::ListValue* args = nullptr;
257 if (syscall_dict.GetList("args", &args)) {
258 for (size_t i = 0; i < args->GetSize(); ++i) {
259 const base::DictionaryValue* args_dict = nullptr;
260 if (!args->GetDictionary(i, &args_dict)) {
261 LOG(ERROR) << "Failed to pars args dict for " << syscall_out->name;
262 return false;
263 }
264 OciSeccompArg this_arg;
265 if (!ParseUint32FromDict(*args_dict, "index", &this_arg.index))
266 return false;
267 if (!ParseUint64FromDict(*args_dict, "value", &this_arg.value))
268 return false;
269 if (!ParseUint64FromDict(*args_dict, "value2", &this_arg.value2))
270 return false;
271 if (!args_dict->GetString("op", &this_arg.op)) {
272 LOG(ERROR) << "Failed to parse op for arg " << this_arg.index
273 << " of " << syscall_out->name;
274 return false;
275 }
276 syscall_out->args.push_back(this_arg);
277 }
278 }
279 return true;
280}
281
282// Parses the seccomp node if it is present.
283bool ParseSeccompInfo(const base::DictionaryValue& seccomp_dict,
284 OciSeccomp* seccomp_out) {
285 if (!seccomp_dict.GetString("defaultAction",
286 &seccomp_out->defaultAction))
287 return false;
288
289 // Gets the list of architectures.
290 const base::ListValue* architectures = nullptr;
291 if (!seccomp_dict.GetList("architectures", &architectures)) {
292 LOG(ERROR) << "Fail to read seccomp architectures";
293 return false;
294 }
295 for (size_t i = 0; i < architectures->GetSize(); ++i) {
296 std::string this_arch;
297 if (!architectures->GetString(i, &this_arch)) {
298 LOG(ERROR) << "Fail to parse seccomp architecture list";
299 return false;
300 }
301 seccomp_out->architectures.push_back(this_arch);
302 }
303
304 // Gets the list of syscalls.
305 const base::ListValue* syscalls = nullptr;
306 if (!seccomp_dict.GetList("syscalls", &syscalls)) {
307 LOG(ERROR) << "Fail to read seccomp syscalls";
308 return false;
309 }
310 for (size_t i = 0; i < syscalls->GetSize(); ++i) {
311 const base::DictionaryValue* syscall_dict = nullptr;
312 if (!syscalls->GetDictionary(i, &syscall_dict)) {
313 LOG(ERROR) << "Fail to parse seccomp syscalls list";
314 return false;
315 }
316 OciSeccompSyscall this_syscall;
317 if (!syscall_dict->GetString("name", &this_syscall.name)) {
318 LOG(ERROR) << "Fail to parse syscall name " << i;
319 return false;
320 }
321 if (!syscall_dict->GetString("action", &this_syscall.action)) {
322 LOG(ERROR) << "Fail to parse syscall action for " << this_syscall.name;
323 return false;
324 }
325 if (!ParseSeccompArgs(*syscall_dict, &this_syscall))
326 return false;
327 seccomp_out->syscalls.push_back(this_syscall);
328 }
329
330 return true;
331}
332
Dylan Reid6b590e62016-10-27 19:10:53 -0700333// Parses the linux node which has information about setting up a user
334// namespace, and the list of devices for the container.
335bool ParseLinuxConfigDict(const base::DictionaryValue& runtime_root_dict,
336 OciConfigPtr const& config_out) {
337 // |linux_dict| is owned by |runtime_root_dict|
338 const base::DictionaryValue* linux_dict = nullptr;
339 if (!runtime_root_dict.GetDictionary("linux", &linux_dict)) {
340 LOG(ERROR) << "Fail to get linux dictionary from the runtime dictionary";
341 return false;
342 }
343
344 // |uid_map_list| is owned by |linux_dict|
345 const base::ListValue* uid_map_list = nullptr;
346 if (!linux_dict->GetList("uidMappings", &uid_map_list)) {
347 LOG(ERROR) << "Fail to get uid mappings list";
348 return false;
349 }
Ben Chanf43980b2017-03-10 11:31:46 -0800350 ParseLinuxIdMappings(uid_map_list, &config_out->linux_config.uidMappings);
Dylan Reid6b590e62016-10-27 19:10:53 -0700351
352 // |gid_map_list| is owned by |linux_dict|
353 const base::ListValue* gid_map_list = nullptr;
354 if (!linux_dict->GetList("gidMappings", &gid_map_list)) {
355 LOG(ERROR) << "Fail to get gid mappings list";
356 return false;
357 }
Ben Chanf43980b2017-03-10 11:31:46 -0800358 ParseLinuxIdMappings(gid_map_list, &config_out->linux_config.gidMappings);
Dylan Reid6b590e62016-10-27 19:10:53 -0700359
360 if (!ParseDeviceList(*linux_dict, config_out))
361 return false;
362
Dylan Reidb0a72772016-11-03 16:27:50 +0000363 const base::DictionaryValue* seccomp_dict = nullptr;
364 if (linux_dict->GetDictionary("seccomp", &seccomp_dict)) {
365 if (!ParseSeccompInfo(*seccomp_dict, &config_out->linux_config.seccomp))
366 return false;
367 }
368
Dylan Reid6b590e62016-10-27 19:10:53 -0700369 return true;
370}
371
Dylan Reid45e34fe2016-12-02 15:11:53 -0800372bool HostnameValid(const std::string& hostname) {
373 if (hostname.length() > 255)
374 return false;
375
376 const std::regex name("^[0-9a-zA-Z]([0-9a-zA-Z-]*[0-9a-zA-Z])?$");
377 if (!std::regex_match(hostname, name))
378 return false;
379
380 const std::regex double_dash("--");
381 if (std::regex_match(hostname, double_dash))
382 return false;
383
384 return true;
385}
386
Dylan Reid6b590e62016-10-27 19:10:53 -0700387// Parses the configuration file for the container. The config file specifies
388// basic filesystem info and details about the process to be run. namespace,
389// cgroup, and syscall configurations are also specified
390bool ParseConfigDict(const base::DictionaryValue& config_root_dict,
391 OciConfigPtr const& config_out) {
Dylan Reid6d650742016-11-02 23:10:38 -0700392 if (!config_root_dict.GetString("ociVersion", &config_out->ociVersion)) {
Dylan Reid6b590e62016-10-27 19:10:53 -0700393 LOG(ERROR) << "Failed to parse ociVersion";
394 return false;
395 }
396 if (!config_root_dict.GetString("hostname", &config_out->hostname)) {
397 LOG(ERROR) << "Failed to parse hostname";
398 return false;
399 }
Dylan Reid45e34fe2016-12-02 15:11:53 -0800400 if (!HostnameValid(config_out->hostname)) {
401 LOG(ERROR) << "Invalid hostname " << config_out->hostname;
402 return false;
403 }
Dylan Reid6b590e62016-10-27 19:10:53 -0700404
405 // Platform info
406 if (!ParsePlatformConfig(config_root_dict, config_out)) {
407 return false;
408 }
409
410 // Root fs info
411 if (!ParseRootFileSystemConfig(config_root_dict, config_out)) {
412 return false;
413 }
414
415 // Process info
416 if (!ParseProcessConfig(config_root_dict, config_out)) {
417 return false;
418 }
419
420 // Get a list of mount points and mounts.
421 if (!ParseMounts(config_root_dict, config_out)) {
422 LOG(ERROR) << "Failed to parse mounts";
423 return false;
424 }
425
426 // Parse linux node.
427 if (!ParseLinuxConfigDict(config_root_dict, config_out)) {
428 LOG(ERROR) << "Failed to parse the linux node";
429 return false;
430 }
431
432 return true;
433}
434
Ben Chanf43980b2017-03-10 11:31:46 -0800435} // anonymous namespace
Dylan Reid6b590e62016-10-27 19:10:53 -0700436
437bool ParseContainerConfig(const std::string& config_json_data,
438 OciConfigPtr const& config_out) {
439 std::unique_ptr<const base::Value> config_root_val =
440 base::JSONReader::Read(config_json_data);
441 if (!config_root_val) {
442 LOG(ERROR) << "Fail to parse config.json";
443 return false;
444 }
445 const base::DictionaryValue* config_dict = nullptr;
446 if (!config_root_val->GetAsDictionary(&config_dict)) {
447 LOG(ERROR) << "Fail to parse root dictionary from config.json";
448 return false;
449 }
450 if (!ParseConfigDict(*config_dict, config_out)) {
451 return false;
452 }
453
454 return true;
455}
456
Ben Chanf43980b2017-03-10 11:31:46 -0800457} // namespace container_utils