discover: pxe: Avoid dereferencing null pointer
[petitboot] / discover / syslinux-parser.c
1 #if defined(HAVE_CONFIG_H)
2 #include "config.h"
3 #endif
4
5 #include <assert.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <i18n/i18n.h>
9 #include <libgen.h>
10
11 #include "log/log.h"
12 #include "list/list.h"
13 #include "file/file.h"
14 #include "talloc/talloc.h"
15 #include "types/types.h"
16 #include "parser-conf.h"
17 #include "parser-utils.h"
18 #include "resource.h"
19 #include "paths.h"
20
21 struct syslinux_boot_option {
22         char *label;
23         char *image;
24         char *append;
25         char *initrd;
26         struct list_item list;
27 };
28
29 /* by spec 16 is allowed */
30 #define INCLUDE_NEST_LIMIT 16
31
32 struct syslinux_options {
33         struct list processed_options;
34         struct syslinux_boot_option *current_option;
35         int include_nest_level;
36         char *cfg_dir;
37 };
38
39
40 static const char *const syslinux_conf_files[] = {
41         "/boot/syslinux/syslinux.cfg",
42         "/syslinux/syslinux.cfg",
43         "/syslinux.cfg",
44         "/BOOT/SYSLINUX/SYSLINUX.CFG",
45         "/SYSLINUX/SYSLINUX.CFG",
46         "/SYSLINUX.CFG",
47         NULL
48 };
49
50 static const char *const syslinux_kernel_unsupported_extensions[] = {
51         ".0", /* eventually support PXE here? */
52         ".bin",
53         ".bs",
54         ".bss",
55         ".c32",
56         ".cbt",
57         ".com",
58         ".img",
59         NULL
60 };
61
62 static const char *const syslinux_ignored_names[] = {
63         "config",
64         "sysapend",
65         "localboot",
66         "ui",
67         "prompt",
68         "noescape",
69         "nocomplete",
70         "allowoptions",
71         "timeout",
72         "totaltimeout",
73         "ontimeout",
74         "onerror",
75         "serial",
76         "nohalt",
77         "console",
78         "font",
79         "kbdmap",
80         "say",
81         "display",
82         "f1",
83         "f2",
84         "f3",
85         "f4",
86         "f5",
87         "f6",
88         "f7",
89         "f8",
90         "f9",
91         "f10",
92         "f11",
93         "f12",
94         NULL
95 };
96
97 static const char *const syslinux_unsupported_boot_names[] = {
98         "boot",
99         "bss",
100         "pxe",
101         "fdimage",
102         "comboot",
103         "com32",
104         NULL
105 };
106
107 static struct conf_global_option syslinux_global_options[] = {
108         { .name = "default" },
109         { .name = "implicit" },
110         { .name = "append" },
111         { .name = NULL }
112 };
113
114
115 static void finish_boot_option(struct syslinux_options *state,
116                                bool free_if_unused)
117 {
118         /*
119          * in the normal this function signals a new image block which means
120          * move the current block to the list of processed items
121          * the special case is a label before an image block which we need to
122          * know whether to keep it for further processing or junk it
123          */
124         if (state->current_option) {
125                 if (state->current_option->image) {
126                         list_add(&state->processed_options,
127                                  &state->current_option->list);
128                         state->current_option = NULL;
129                 } else if (free_if_unused) {
130                         talloc_free(state->current_option);
131                         state->current_option = NULL;
132                 }
133         }
134 }
135
136 static bool start_new_option(struct syslinux_options *state)
137 {
138         bool ret = false;
139
140         finish_boot_option(state, false);
141         if (!state->current_option)
142                 state->current_option = talloc_zero(state, struct syslinux_boot_option);
143
144         if (state->current_option)
145                 ret = true;
146
147         return ret;
148 }
149
150 static void syslinux_process_pair(struct conf_context *conf, const char *name, char *value)
151 {
152         struct syslinux_options *state = conf->parser_info;
153         char *buf, *pos, *path;
154         int len, rc;
155
156         /* ignore bare values */
157         if (!name)
158                 return;
159
160         if (conf_param_in_list(syslinux_ignored_names, name))
161                 return;
162
163         /* a new boot entry needs to terminate any prior one */
164         if (conf_param_in_list(syslinux_unsupported_boot_names, name)) {
165                 finish_boot_option(state, true);
166                 return;
167         }
168
169         if (streq(name, "label")) {
170                 finish_boot_option(state, true);
171                 state->current_option = talloc_zero(state,
172                                             struct syslinux_boot_option);
173                 if (state->current_option)
174                         state->current_option->label = talloc_strdup(state, value);
175                 return;
176         }
177
178         if (streq(name, "linux")) {
179
180                 if (start_new_option(state)) {
181                         state->current_option->image = talloc_strdup(state, value);
182                         if (!state->current_option->image) {
183                                 talloc_free(state->current_option);
184                                 state->current_option = NULL;
185                         }
186                 }
187
188                 return;
189         }
190
191         if (streq(name, "kernel")) {
192
193                 if (start_new_option(state)) {
194                 /*
195                  * by spec a linux image can not have any of these
196                  * extensions, it can have no extension or anything not
197                  * in this list
198                  */
199                         pos = strrchr(value, '.');
200                         if (!pos ||
201                         !conf_param_in_list(syslinux_kernel_unsupported_extensions, pos)) {
202                                 state->current_option->image = talloc_strdup(state, value);
203                                 if (!state->current_option->image) {
204                                         talloc_free(state->current_option);
205                                         state->current_option = NULL;
206                                 }
207                         } else  /* clean up any possible trailing label */
208                                 finish_boot_option(state, true);
209                 }
210                 return;
211         }
212
213
214
215         /* APPEND can be global and/or local so need special handling */
216         if (streq(name, "append")) {
217                 if (state->current_option) {
218                         /* by spec only take last if multiple APPENDs */
219                         if (state->current_option->append)
220                                 talloc_free(state->current_option->append);
221                         state->current_option->append = talloc_strdup(state, value);
222                         if (!state->current_option->append) {
223                                 talloc_free(state->current_option);
224                                 state->current_option = NULL;
225                         }
226                 } else {
227                         finish_boot_option(state, true);
228                         conf_set_global_option(conf, name, value);
229                 }
230                 return;
231         }
232
233         /* now the general globals */
234         if (conf_set_global_option(conf, name, value)) {
235                 finish_boot_option(state, true);
236                 return;
237         }
238
239         if (streq(name, "initrd")) {
240                 if (state->current_option) {
241                         state->current_option->initrd = talloc_strdup(state, value);
242                         if (!state->current_option->initrd) {
243                                 talloc_free(state->current_option);
244                                 state->current_option = NULL;
245                         }
246                 }
247                 return;
248         }
249
250         if (streq(name, "include")) {
251                 if (state->include_nest_level < INCLUDE_NEST_LIMIT) {
252                         state->include_nest_level++;
253
254                         /* if absolute in as-is */
255                         if (value[0] == '/')
256                                 path = talloc_strdup(state, value);
257                         else /* otherwise relative to the root config file */
258                                 path = join_paths(state, state->cfg_dir, value);
259
260                         rc = parser_request_file(conf->dc, conf->dc->device, path, &buf, &len);
261                         if (!rc) {
262                                 conf_parse_buf(conf, buf, len);
263
264                                 device_handler_status_dev_info(conf->dc->handler, conf->dc->device,
265                                 _("Parsed nested syslinux configuration from %s"), value);
266                                 talloc_free(buf);
267                         } else {
268                                 device_handler_status_dev_info(conf->dc->handler, conf->dc->device,
269                                 _("Failed to parse nested syslinux configuration from %s"), value);
270                         }
271
272                         talloc_free(path);
273
274                         state->include_nest_level--;
275                 } else {
276                         device_handler_status_dev_err(conf->dc->handler, conf->dc->device,
277                                 _("Nested syslinux INCLUDE exceeds limit...ignored"));
278                 }
279                 return;
280         }
281
282         pb_debug("%s: unknown name: %s\n", __func__, name);
283 }
284
285 static void syslinux_finalize(struct conf_context *conf)
286 {
287         struct syslinux_options *state = conf->parser_info;
288         struct syslinux_boot_option *syslinux_opt;
289         struct discover_context *dc = conf->dc;
290         struct discover_boot_option *d_opt;
291         bool implicit_image = true;
292         char *args_sigfile_default;
293         const char *global_default;
294         const char *global_append;
295         struct boot_option *opt;
296         const char *image;
297         const char *label;
298
299         /* clean up any lingering boot entries */
300         finish_boot_option(state, true);
301
302         global_append  = conf_get_global_option(conf, "append");
303         global_default = conf_get_global_option(conf, "default");
304
305         /*
306          * by spec '0' means disable
307          * note we set the default to '1' (which is by spec) in syslinux_parse
308          */
309         if (conf_get_global_option(conf, "implicit"), "0")
310                 implicit_image = false;
311
312         list_for_each_entry(&state->processed_options, syslinux_opt, list) {
313                 /* need a valid image */
314                 if (!syslinux_opt->image)
315                         continue;
316
317                 image = syslinux_opt->image;
318                 label = syslinux_opt->label;
319
320                 /* if implicit is disabled we must have a label */
321                 if (!label && !implicit_image)
322                         continue;
323
324                 d_opt = discover_boot_option_create(dc, dc->device);
325                 if (!d_opt)
326                         continue;
327                 if (!d_opt->option)
328                         goto fail;
329
330                 opt = d_opt->option;
331
332                 if (syslinux_opt->append) {
333                         /* '-' can signal do not use global APPEND */
334                         if (!strcmp(syslinux_opt->append, "-"))
335                                 opt->boot_args = talloc_strdup(opt, "");
336                         else
337                                 opt->boot_args = talloc_asprintf(opt, "%s %s",
338                                                                  global_append,
339                                                                  syslinux_opt->append);
340                 } else
341                         opt->boot_args = talloc_strdup(opt, global_append);
342
343                 if (!opt->boot_args)
344                         goto fail;
345
346                 opt->id = talloc_asprintf(opt, "%s#%s", dc->device->device->id, image);
347
348                 if (!opt->id)
349                         goto fail;
350
351                 if (label) {
352                         opt->name = talloc_strdup(opt, label);
353                         if (!strcmp(label, global_default))
354                                 opt->is_default = true;
355
356                         opt->description = talloc_asprintf(opt, "(%s) %s", label, image);
357                 } else {
358                         opt->name = talloc_strdup(opt, image);
359                         opt->description = talloc_strdup(opt, image);
360                 }
361
362                 if (!opt->name)
363                         goto fail;
364
365                 d_opt->boot_image = create_devpath_resource(d_opt, dc->device, image);
366
367                 if(!d_opt->boot_image)
368                         goto fail;
369
370                 if (syslinux_opt->initrd) {
371                         d_opt->initrd = create_devpath_resource(d_opt, dc->device,
372                                                                 syslinux_opt->initrd);
373                         opt->description = talloc_asprintf_append(opt->description, 
374                                                                   " initrd=%s",
375                                                                   syslinux_opt->initrd);
376
377                         if (!d_opt->initrd)
378                                 goto fail;
379                 }
380
381                 opt->description = talloc_asprintf_append(opt->description,
382                                                           " args=%s",
383                                                           opt->boot_args);
384
385                 if (!opt->description)
386                         goto fail;
387
388                 args_sigfile_default = talloc_asprintf(d_opt, "%s.cmdline.sig",
389                                                        image);
390
391                 if (!args_sigfile_default)
392                         goto fail;
393
394                 d_opt->args_sig_file = create_devpath_resource(d_opt, dc->device,
395                                                                args_sigfile_default);
396
397                 if (!d_opt->args_sig_file)
398                         goto fail;
399
400                 talloc_free(args_sigfile_default);
401
402                 conf_strip_str(opt->boot_args);
403                 conf_strip_str(opt->description);
404
405                 discover_context_add_boot_option(dc, d_opt);
406                 continue;
407 fail:
408                 talloc_free(d_opt);
409         }
410 }
411
412 static int syslinux_parse(struct discover_context *dc)
413 {
414         struct syslinux_options *state;
415         const char * const *filename;
416         struct conf_context *conf;
417         char *cfg_dir;
418         int len, rc;
419         char *buf;
420
421         /* Support block device boot only at present */
422         if (dc->event)
423                 return -1;
424
425         conf = talloc_zero(dc, struct conf_context);
426
427         if (!conf)
428                 return -1;
429
430         conf->dc = dc;
431         conf->global_options = syslinux_global_options,
432         conf_init_global_options(conf);
433         conf->get_pair = conf_get_pair_space;
434         conf->process_pair = syslinux_process_pair;
435
436         conf->parser_info = state = talloc_zero(conf, struct syslinux_options);
437         list_init(&state->processed_options);
438
439         /*
440          * set the global defaults
441          * by spec 'default' defaults to 'linux' and
442          * 'implicit' defaults to '1', we also just set
443          * and empty string in 'append' to make it easier
444          * in syslinux_finish
445          */
446         conf_set_global_option(conf, "default", "linux");
447         conf_set_global_option(conf, "implicit", "1");
448         conf_set_global_option(conf, "append", "");
449
450         for (filename = syslinux_conf_files; *filename; filename++) {
451                 rc = parser_request_file(dc, dc->device, *filename, &buf, &len);
452                 if (rc)
453                         continue;
454
455                 /*
456                  * save location of root config file for possible
457                  * INCLUDE directives later
458                  *
459                  * dirname can overwrite so need local copy to work on
460                  */
461                 cfg_dir = talloc_strdup(conf, *filename);
462                 state->cfg_dir = talloc_strdup(state, dirname(cfg_dir));
463                 talloc_free(cfg_dir);
464
465                 conf_parse_buf(conf, buf, len);
466                 device_handler_status_dev_info(dc->handler, dc->device,
467                                 _("Parsed syslinux configuration from %s"),
468                                 *filename);
469                 talloc_free(buf);
470
471                 syslinux_finalize(conf);
472         }
473
474         talloc_free(conf);
475         return 0;
476 }
477
478 static struct parser syslinux_parser = {
479         .name                   = "syslinux",
480         .parse                  = syslinux_parse,
481         .resolve_resource       = resolve_devpath_resource,
482 };
483
484 register_parser(syslinux_parser);