loadenv.c 11.2 KB
Newer Older
1 2 3
/* loadenv.c - command to load/save environment variable.  */
/*
 *  GRUB  --  GRand Unified Bootloader
4
 *  Copyright (C) 2008,2009,2010  Free Software Foundation, Inc.
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
 *
 *  GRUB is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  GRUB is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <grub/dl.h>
#include <grub/mm.h>
#include <grub/file.h>
#include <grub/disk.h>
#include <grub/misc.h>
#include <grub/env.h>
#include <grub/partition.h>
27
#include <grub/lib/envblk.h>
28
#include <grub/extcmd.h>
29
#include <grub/i18n.h>
30

31 32
GRUB_MOD_LICENSE ("GPLv3+");

33 34
static const struct grub_arg_option options[] =
  {
35 36
    /* TRANSLATORS: This option is used to override default filename
       for loading and storing environment.  */
37
    {"file", 'f', 0, N_("Specify filename."), 0, ARG_TYPE_PATHNAME},
38 39
    {"skip-sig", 's', 0,
     N_("Skip signature-checking of the environment file."), 0, ARG_TYPE_NONE},
40 41 42
    {0, 0, 0, 0, 0, 0}
  };

43 44 45
/* Opens 'filename' with compression filters disabled. Optionally disables the
   PUBKEY filter (that insists upon properly signed files) as well.  PUBKEY
   filter is restored before the function returns. */
46
static grub_file_t
47
open_envblk_file (char *filename, int untrusted)
48 49
{
  grub_file_t file;
50
  char *buf = 0;
51 52 53

  if (! filename)
    {
54
      const char *prefix;
55
      int len;
56 57

      prefix = grub_env_get ("prefix");
58
      if (! prefix)
59
        {
60
          grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("variable `%s' isn't set"), "prefix");
61 62
          return 0;
        }
63 64 65 66 67 68 69 70 71 72

      len = grub_strlen (prefix);
      buf = grub_malloc (len + 1 + sizeof (GRUB_ENVBLK_DEFCFG));
      if (! buf)
        return 0;
      filename = buf;

      grub_strcpy (filename, prefix);
      filename[len] = '/';
      grub_strcpy (filename + len + 1, GRUB_ENVBLK_DEFCFG);
73 74
    }

75 76
  /* The filters that are disabled will be re-enabled by the call to
     grub_file_open() after this particular file is opened. */
77
  grub_file_filter_disable_compression ();
78 79 80 81 82 83 84
  if (untrusted)
    grub_file_filter_disable_pubkey ();

  file = grub_file_open (filename);

  grub_free (buf);
  return file;
85 86 87 88 89 90 91 92 93
}

static grub_envblk_t
read_envblk_file (grub_file_t file)
{
  grub_off_t offset = 0;
  char *buf;
  grub_size_t size = grub_file_size (file);
  grub_envblk_t envblk;
94

95 96 97
  buf = grub_malloc (size);
  if (! buf)
    return 0;
98

99
  while (size > 0)
100
    {
101 102 103 104
      grub_ssize_t ret;

      ret = grub_file_read (file, buf + offset, size);
      if (ret <= 0)
105
        {
106
          grub_free (buf);
107 108 109
          return 0;
        }

110 111
      size -= ret;
      offset += ret;
112 113
    }

114
  envblk = grub_envblk_open (buf, offset);
115 116
  if (! envblk)
    {
117 118
      grub_free (buf);
      grub_error (GRUB_ERR_BAD_FILE_TYPE, "invalid environment block");
119 120 121
      return 0;
    }

122
  return envblk;
123 124
}

125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
struct grub_env_whitelist
{
  grub_size_t len;
  char **list;
};
typedef struct grub_env_whitelist grub_env_whitelist_t;

static int
test_whitelist_membership (const char* name,
                           const grub_env_whitelist_t* whitelist)
{
  grub_size_t i;

  for (i = 0; i < whitelist->len; i++)
    if (grub_strcmp (name, whitelist->list[i]) == 0)
      return 1;  /* found it */

  return 0;  /* not found */
}

145 146
/* Helper for grub_cmd_load_env.  */
static int
147
set_var (const char *name, const char *value, void *whitelist)
148
{
149 150 151 152 153 154 155 156 157 158
  if (! whitelist)
    {
      grub_env_set (name, value);
      return 0;
    }

  if (test_whitelist_membership (name,
				 (const grub_env_whitelist_t *) whitelist))
    grub_env_set (name, value);

159 160 161
  return 0;
}

162
static grub_err_t
163
grub_cmd_load_env (grub_extcmd_context_t ctxt, int argc, char **args)
164
{
165
  struct grub_arg_list *state = ctxt->state;
166
  grub_file_t file;
167
  grub_envblk_t envblk;
168 169 170 171
  grub_env_whitelist_t whitelist;

  whitelist.len = argc;
  whitelist.list = args;
172

173 174
  /* state[0] is the -f flag; state[1] is the --skip-sig flag */
  file = open_envblk_file ((state[0].set) ? state[0].arg : 0, state[1].set);
175 176 177
  if (! file)
    return grub_errno;

178 179 180
  envblk = read_envblk_file (file);
  if (! envblk)
    goto fail;
181

182 183
  /* argc > 0 indicates caller provided a whitelist of variables to read. */
  grub_envblk_iterate (envblk, argc > 0 ? &whitelist : 0, set_var);
184
  grub_envblk_close (envblk);
185

186 187
 fail:
  grub_file_close (file);
188 189 190
  return grub_errno;
}

191 192
/* Print all variables in current context.  */
static int
193 194
print_var (const char *name, const char *value,
           void *hook_data __attribute__ ((unused)))
195 196 197 198 199
{
  grub_printf ("%s=%s\n", name, value);
  return 0;
}

200
static grub_err_t
201
grub_cmd_list_env (grub_extcmd_context_t ctxt,
202 203
		   int argc __attribute__ ((unused)),
		   char **args __attribute__ ((unused)))
204
{
205
  struct grub_arg_list *state = ctxt->state;
206
  grub_file_t file;
207
  grub_envblk_t envblk;
208

209
  file = open_envblk_file ((state[0].set) ? state[0].arg : 0, 0);
210 211 212
  if (! file)
    return grub_errno;

213 214 215 216
  envblk = read_envblk_file (file);
  if (! envblk)
    goto fail;

217
  grub_envblk_iterate (envblk, NULL, print_var);
218
  grub_envblk_close (envblk);
219

220
 fail:
221
  grub_file_close (file);
222 223
  return grub_errno;
}
224

225 226 227 228 229 230 231 232 233 234 235 236 237
/* Used to maintain a variable length of blocklists internally.  */
struct blocklist
{
  grub_disk_addr_t sector;
  unsigned offset;
  unsigned length;
  struct blocklist *next;
};

static void
free_blocklists (struct blocklist *p)
{
  struct blocklist *q;
238

239 240 241 242 243 244 245
  for (; p; p = q)
    {
      q = p->next;
      grub_free (p);
    }
}

246
static grub_err_t
247 248 249 250 251 252 253 254 255
check_blocklists (grub_envblk_t envblk, struct blocklist *blocklists,
                  grub_file_t file)
{
  grub_size_t total_length;
  grub_size_t index;
  grub_disk_t disk;
  grub_disk_addr_t part_start;
  struct blocklist *p;
  char *buf;
256

257 258 259 260 261
  /* Sanity checks.  */
  total_length = 0;
  for (p = blocklists; p; p = p->next)
    {
      struct blocklist *q;
262
      /* Check if any pair of blocks overlap.  */
263 264
      for (q = p->next; q; q = q->next)
        {
265
	  grub_disk_addr_t s1, s2;
266
	  grub_disk_addr_t e1, e2;
267 268 269 270 271 272 273

	  s1 = p->sector;
	  e1 = s1 + ((p->length + GRUB_DISK_SECTOR_SIZE - 1) >> GRUB_DISK_SECTOR_BITS);

	  s2 = q->sector;
	  e2 = s2 + ((q->length + GRUB_DISK_SECTOR_SIZE - 1) >> GRUB_DISK_SECTOR_BITS);

274
	  if (s1 < e2 && s2 < e1)
275 276 277
            {
              /* This might be actually valid, but it is unbelievable that
                 any filesystem makes such a silly allocation.  */
278
              return grub_error (GRUB_ERR_BAD_FS, "malformed file");
279 280
            }
        }
281

282 283
      total_length += p->length;
    }
284

285 286 287
  if (total_length != grub_file_size (file))
    {
      /* Maybe sparse, unallocated sectors. No way in GRUB.  */
288
      return grub_error (GRUB_ERR_BAD_FILE_TYPE, "sparse file not allowed");
289 290 291 292 293
    }

  /* One more sanity check. Re-read all sectors by blocklists, and compare
     those with the data read via a file.  */
  disk = file->device->disk;
294 295

  part_start = grub_partition_get_start (disk->partition);
296 297

  buf = grub_envblk_buffer (envblk);
298 299
  char *blockbuf = NULL;
  grub_size_t blockbuf_len = 0;
300
  for (p = blocklists, index = 0; p; index += p->length, p = p->next)
301
    {
302 303 304 305 306 307 308 309
      if (p->length > blockbuf_len)
	{
	  grub_free (blockbuf);
	  blockbuf_len = 2 * p->length;
	  blockbuf = grub_malloc (blockbuf_len);
	  if (!blockbuf)
	    return grub_errno;
	}
310

311 312
      if (grub_disk_read (disk, p->sector - part_start,
                          p->offset, p->length, blockbuf))
313
        return grub_errno;
314 315

      if (grub_memcmp (buf + index, blockbuf, p->length) != 0)
316
	return grub_error (GRUB_ERR_FILE_READ_ERROR, "invalid blocklist");
317 318
    }

319
  return GRUB_ERR_NONE;
320 321 322 323 324 325 326 327 328 329 330
}

static int
write_blocklists (grub_envblk_t envblk, struct blocklist *blocklists,
                  grub_file_t file)
{
  char *buf;
  grub_disk_t disk;
  grub_disk_addr_t part_start;
  struct blocklist *p;
  grub_size_t index;
331

332 333
  buf = grub_envblk_buffer (envblk);
  disk = file->device->disk;
334
  part_start = grub_partition_get_start (disk->partition);
335 336

  index = 0;
337
  for (p = blocklists; p; index += p->length, p = p->next)
338 339 340 341 342 343 344
    {
      if (grub_disk_write (disk, p->sector - part_start,
                           p->offset, p->length, buf + index))
        return 0;
    }

  return 1;
345 346
}

347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377
/* Context for grub_cmd_save_env.  */
struct grub_cmd_save_env_ctx
{
  struct blocklist *head, *tail;
};

/* Store blocklists in a linked list.  */
static void
save_env_read_hook (grub_disk_addr_t sector, unsigned offset, unsigned length,
		    void *data)
{
  struct grub_cmd_save_env_ctx *ctx = data;
  struct blocklist *block;

  block = grub_malloc (sizeof (*block));
  if (! block)
    return;

  block->sector = sector;
  block->offset = offset;
  block->length = length;

  /* Slightly complicated, because the list should be FIFO.  */
  block->next = 0;
  if (ctx->tail)
    ctx->tail->next = block;
  ctx->tail = block;
  if (! ctx->head)
    ctx->head = block;
}

378
static grub_err_t
379
grub_cmd_save_env (grub_extcmd_context_t ctxt, int argc, char **args)
380
{
381
  struct grub_arg_list *state = ctxt->state;
382
  grub_file_t file;
383
  grub_envblk_t envblk;
384 385 386 387
  struct grub_cmd_save_env_ctx ctx = {
    .head = 0,
    .tail = 0
  };
388 389

  if (! argc)
390
    return grub_error (GRUB_ERR_BAD_ARGUMENT, "no variable is specified");
391

392 393
  file = open_envblk_file ((state[0].set) ? state[0].arg : 0,
                           1 /* allow untrusted */);
394 395 396
  if (! file)
    return grub_errno;

397
  if (! file->device->disk)
398
    {
399 400
      grub_file_close (file);
      return grub_error (GRUB_ERR_BAD_DEVICE, "disk device required");
401 402
    }

403 404
  file->read_hook = save_env_read_hook;
  file->read_hook_data = &ctx;
405 406 407 408
  envblk = read_envblk_file (file);
  file->read_hook = 0;
  if (! envblk)
    goto fail;
409

410
  if (check_blocklists (envblk, ctx.head, file))
411
    goto fail;
412

413 414
  while (argc)
    {
415
      const char *value;
416 417 418 419

      value = grub_env_get (args[0]);
      if (value)
        {
420
          if (! grub_envblk_set (envblk, args[0], value))
421 422
            {
              grub_error (GRUB_ERR_BAD_ARGUMENT, "environment block too small");
423
              goto fail;
424 425
            }
        }
426 427
      else
	grub_envblk_delete (envblk, args[0]);
428 429 430 431 432

      argc--;
      args++;
    }

433
  write_blocklists (envblk, ctx.head, file);
434

435 436 437
 fail:
  if (envblk)
    grub_envblk_close (envblk);
438
  free_blocklists (ctx.head);
439 440 441 442
  grub_file_close (file);
  return grub_errno;
}

443 444
static grub_extcmd_t cmd_load, cmd_list, cmd_save;

445 446
GRUB_MOD_INIT(loadenv)
{
447
  cmd_load =
448
    grub_register_extcmd ("load_env", grub_cmd_load_env, 0,
449
			  N_("[-f FILE] [-s|--skip-sig] [variable_name_to_whitelist] [...]"),
450
			  N_("Load variables from environment block file."),
451 452
			  options);
  cmd_list =
453
    grub_register_extcmd ("list_env", grub_cmd_list_env, 0, N_("[-f FILE]"),
454
			  N_("List variables from environment block file."),
455 456
			  options);
  cmd_save =
457
    grub_register_extcmd ("save_env", grub_cmd_save_env, 0,
458 459
			  N_("[-f FILE] variable_name [...]"),
			  N_("Save variables to environment block file."),
460
			  options);
461 462 463 464
}

GRUB_MOD_FINI(loadenv)
{
465 466 467
  grub_unregister_extcmd (cmd_load);
  grub_unregister_extcmd (cmd_list);
  grub_unregister_extcmd (cmd_save);
468
}