github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/boot/syslinux/syslinux_test.go (about) 1 // Copyright 2017-2018 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package syslinux 6 7 import ( 8 "bytes" 9 "context" 10 "fmt" 11 "io" 12 "log" 13 "net/url" 14 "os" 15 "path/filepath" 16 "reflect" 17 "strings" 18 "testing" 19 20 "github.com/mvdan/u-root-coreutils/pkg/boot" 21 "github.com/mvdan/u-root-coreutils/pkg/boot/boottest" 22 "github.com/mvdan/u-root-coreutils/pkg/boot/linux" 23 "github.com/mvdan/u-root-coreutils/pkg/boot/multiboot" 24 "github.com/mvdan/u-root-coreutils/pkg/curl" 25 ) 26 27 func mustParseURL(s string) *url.URL { 28 u, err := url.Parse(s) 29 if err != nil { 30 panic(fmt.Sprintf("parsing %q failed: %v", s, err)) 31 } 32 return u 33 } 34 35 type errorReader struct { 36 err error 37 } 38 39 func (e errorReader) ReadAt(p []byte, n int64) (int, error) { 40 return 0, e.err 41 } 42 43 func TestParseGeneral(t *testing.T) { 44 kernel1 := "kernel1" 45 kernel2 := "kernel2" 46 globalInitrd := "globalInitrd" 47 initrd1 := "initrd1" 48 initrd2 := "initrd2" 49 xengz := "xengz" 50 mboot := "mboot.c32" 51 boardDTB := "board.dtb" 52 53 newMockScheme := func() *curl.MockScheme { 54 fs := curl.NewMockScheme("tftp") 55 fs.Add("1.2.3.4", "/foobar/pxelinux.0", "") 56 fs.Add("1.2.3.4", "/foobar/pxefiles/kernel1", kernel1) 57 fs.Add("1.2.3.4", "/foobar/pxefiles/kernel2", kernel2) 58 fs.Add("1.2.3.4", "/foobar/pxefiles/global_initrd", globalInitrd) 59 fs.Add("1.2.3.4", "/foobar/pxefiles/initrd1", initrd1) 60 fs.Add("1.2.3.4", "/foobar/pxefiles/initrd2", initrd2) 61 fs.Add("1.2.3.4", "/foobar/pxefiles/board.dtb", boardDTB) 62 fs.Add("1.2.3.4", "/foobar/xen.gz", xengz) 63 fs.Add("1.2.3.4", "/foobar/mboot.c32", mboot) 64 65 fs.Add("2.3.4.5", "/barfoo/pxefiles/kernel1", kernel1) 66 return fs 67 } 68 http := curl.NewMockScheme("http") 69 http.Add("someplace.com", "/initrd2", initrd2) 70 71 for i, tt := range []struct { 72 desc string 73 configFiles map[string]string 74 want []boot.OSImage 75 err error 76 }{ 77 { 78 desc: "all files exist, simple config with cmdline initrd", 79 configFiles: map[string]string{ 80 "/foobar/pxelinux.cfg/default": ` 81 default foo 82 label foo 83 kernel ./pxefiles/kernel1 84 append initrd=./pxefiles/global_initrd`, 85 }, 86 want: []boot.OSImage{ 87 &boot.LinuxImage{ 88 Name: "foo", 89 Kernel: strings.NewReader(kernel1), 90 Initrd: strings.NewReader(globalInitrd), 91 Cmdline: "initrd=./pxefiles/global_initrd", 92 }, 93 }, 94 }, 95 { 96 desc: "empty label", 97 configFiles: map[string]string{ 98 "/foobar/pxelinux.cfg/default": ` 99 default foo 100 label foo`, 101 }, 102 want: nil, 103 }, 104 105 { 106 desc: "all files exist, simple config with directive initrd", 107 configFiles: map[string]string{ 108 "/foobar/pxelinux.cfg/default": ` 109 default foo 110 label foo 111 kernel ./pxefiles/kernel1 112 initrd ./pxefiles/initrd1 113 append foo=bar`, 114 }, 115 want: []boot.OSImage{ 116 &boot.LinuxImage{ 117 Name: "foo", 118 Kernel: strings.NewReader(kernel1), 119 Initrd: strings.NewReader(initrd1), 120 Cmdline: "foo=bar", 121 }, 122 }, 123 }, 124 { 125 desc: "all files exist, simple config, no initrd", 126 configFiles: map[string]string{ 127 "/foobar/pxelinux.cfg/default": ` 128 default foo 129 label foo 130 kernel ./pxefiles/kernel1`, 131 }, 132 want: []boot.OSImage{ 133 &boot.LinuxImage{ 134 Name: "foo", 135 Kernel: strings.NewReader(kernel1), 136 Initrd: nil, 137 Cmdline: "", 138 }, 139 }, 140 }, 141 { 142 desc: "all files exist, simple config with two initrd files", 143 configFiles: map[string]string{ 144 "/foobar/pxelinux.cfg/default": ` 145 default foo 146 label multi-initrd 147 kernel ./pxefiles/kernel1 148 initrd ./pxefiles/initrd1,./pxefiles/initrd2 149 append foo=bar`, 150 }, 151 want: []boot.OSImage{ 152 &boot.LinuxImage{ 153 Name: "multi-initrd", 154 Kernel: strings.NewReader(kernel1), 155 Initrd: boot.CatInitrds(strings.NewReader(initrd1), strings.NewReader(initrd2)), 156 Cmdline: "foo=bar", 157 }, 158 }, 159 }, 160 { 161 desc: "an initrd file missing, config with two initrd files", 162 configFiles: map[string]string{ 163 "/foobar/pxelinux.cfg/default": ` 164 default foo 165 label multi-initrd 166 kernel ./pxefiles/kernel1 167 initrd ./pxefiles/initrd1,./pxefiles/no-initrd-here 168 append foo=bar`, 169 }, 170 want: []boot.OSImage{ 171 &boot.LinuxImage{ 172 Name: "multi-initrd", 173 Kernel: strings.NewReader(kernel1), 174 Initrd: errorReader{&curl.URLError{ 175 URL: &url.URL{ 176 Scheme: "tftp", 177 Host: "1.2.3.4", 178 Path: "/foobar/pxefiles/no-initrd-here", 179 }, 180 Err: curl.ErrNoSuchFile, 181 }}, 182 Cmdline: "foo=bar", 183 }, 184 }, 185 }, 186 { 187 desc: "kernel does not exist, simple config", 188 configFiles: map[string]string{ 189 "/foobar/pxelinux.cfg/default": ` 190 default foo 191 label foo 192 kernel ./pxefiles/does-not-exist`, 193 }, 194 want: []boot.OSImage{ 195 &boot.LinuxImage{ 196 Name: "foo", 197 Kernel: errorReader{&curl.URLError{ 198 URL: &url.URL{ 199 Scheme: "tftp", 200 Host: "1.2.3.4", 201 Path: "/foobar/pxefiles/does-not-exist", 202 }, 203 Err: curl.ErrNoSuchFile, 204 }}, 205 Initrd: nil, 206 Cmdline: "", 207 }, 208 }, 209 }, 210 { 211 desc: "config file does not exist", 212 err: &curl.URLError{ 213 URL: &url.URL{ 214 Scheme: "tftp", 215 Host: "1.2.3.4", 216 Path: "/foobar/pxelinux.cfg/default", 217 }, 218 Err: curl.ErrNoSuchFile, 219 }, 220 }, 221 { 222 desc: "empty config", 223 configFiles: map[string]string{ 224 "/foobar/pxelinux.cfg/default": "", 225 }, 226 want: nil, 227 }, 228 { 229 desc: "valid config with two Entries", 230 configFiles: map[string]string{ 231 "/foobar/pxelinux.cfg/default": ` 232 default foo 233 234 label bar 235 menu label Bla Bla Bla 236 kernel ./pxefiles/kernel2 237 append console=ttyS0 238 239 label foo 240 kernel ./pxefiles/kernel1 241 append earlyprintk=ttyS0 printk=ttyS0`, 242 }, 243 want: []boot.OSImage{ 244 &boot.LinuxImage{ 245 Name: "foo", 246 Kernel: strings.NewReader(kernel1), 247 Cmdline: "earlyprintk=ttyS0 printk=ttyS0", 248 }, 249 &boot.LinuxImage{ 250 Name: "Bla Bla Bla", 251 Kernel: strings.NewReader(kernel2), 252 Cmdline: "console=ttyS0", 253 }, 254 }, 255 }, 256 { 257 desc: "menu default, linux directives", 258 configFiles: map[string]string{ 259 "/foobar/pxelinux.cfg/default": ` 260 label bar 261 menu label Bla Bla Bla 262 kernel ./pxefiles/kernel2 263 append console=ttyS0 264 265 label foo 266 menu default 267 linux ./pxefiles/kernel1 268 append earlyprintk=ttyS0 printk=ttyS0`, 269 }, 270 want: []boot.OSImage{ 271 &boot.LinuxImage{ 272 Name: "foo", 273 Kernel: strings.NewReader(kernel1), 274 Cmdline: "earlyprintk=ttyS0 printk=ttyS0", 275 }, 276 &boot.LinuxImage{ 277 Name: "Bla Bla Bla", 278 Kernel: strings.NewReader(kernel2), 279 Cmdline: "console=ttyS0", 280 }, 281 }, 282 }, 283 { 284 desc: "valid config with two Entries, and a nerfdefault override", 285 configFiles: map[string]string{ 286 "/foobar/pxelinux.cfg/default": ` 287 default foo 288 289 nerfdefault bar 290 291 label foo 292 kernel ./pxefiles/kernel1 293 append earlyprintk=ttyS0 printk=ttyS0 294 295 label bar 296 kernel ./pxefiles/kernel2 297 append console=ttyS0`, 298 }, 299 want: []boot.OSImage{ 300 &boot.LinuxImage{ 301 Name: "bar", 302 Kernel: strings.NewReader(kernel2), 303 Cmdline: "console=ttyS0", 304 }, 305 &boot.LinuxImage{ 306 Name: "foo", 307 Kernel: strings.NewReader(kernel1), 308 Cmdline: "earlyprintk=ttyS0 printk=ttyS0", 309 }, 310 }, 311 }, 312 { 313 desc: "valid config with two Entries, and a nerfdefault override, order agnostic", 314 configFiles: map[string]string{ 315 "/foobar/pxelinux.cfg/default": ` 316 nerfdefault bar 317 318 default foo 319 320 label foo 321 kernel ./pxefiles/kernel1 322 append earlyprintk=ttyS0 printk=ttyS0 323 324 label bar 325 kernel ./pxefiles/kernel2 326 append console=ttyS0`, 327 }, 328 want: []boot.OSImage{ 329 &boot.LinuxImage{ 330 Name: "bar", 331 Kernel: strings.NewReader(kernel2), 332 Cmdline: "console=ttyS0", 333 }, 334 &boot.LinuxImage{ 335 Name: "foo", 336 Kernel: strings.NewReader(kernel1), 337 Cmdline: "earlyprintk=ttyS0 printk=ttyS0", 338 }, 339 }, 340 }, 341 342 { 343 desc: "valid config with global APPEND directive", 344 configFiles: map[string]string{ 345 "/foobar/pxelinux.cfg/default": ` 346 default foo 347 append foo=bar 348 349 label foo 350 kernel ./pxefiles/kernel1 351 append earlyprintk=ttyS0 printk=ttyS0 352 353 label bar 354 kernel ./pxefiles/kernel2 355 356 label baz 357 kernel ./pxefiles/kernel2 358 append -`, 359 }, 360 want: []boot.OSImage{ 361 &boot.LinuxImage{ 362 Name: "foo", 363 Kernel: strings.NewReader(kernel1), 364 // Does not contain global APPEND. 365 Cmdline: "earlyprintk=ttyS0 printk=ttyS0", 366 }, 367 &boot.LinuxImage{ 368 Name: "bar", 369 Kernel: strings.NewReader(kernel2), 370 // Contains only global APPEND. 371 Cmdline: "foo=bar", 372 }, 373 &boot.LinuxImage{ 374 Name: "baz", 375 Kernel: strings.NewReader(kernel2), 376 // "APPEND -" means ignore global APPEND. 377 Cmdline: "", 378 }, 379 }, 380 }, 381 { 382 desc: "valid config with global APPEND with initrd", 383 configFiles: map[string]string{ 384 "/foobar/pxelinux.cfg/default": ` 385 default mcnulty 386 append initrd=./pxefiles/global_initrd 387 388 label mcnulty 389 kernel ./pxefiles/kernel1 390 append earlyprintk=ttyS0 printk=ttyS0 391 392 label lester 393 kernel ./pxefiles/kernel1 394 395 label omar 396 kernel ./pxefiles/kernel2 397 append - 398 399 label stringer 400 kernel ./pxefiles/kernel2 401 initrd ./pxefiles/initrd2 402 `, 403 }, 404 want: []boot.OSImage{ 405 &boot.LinuxImage{ 406 Name: "mcnulty", 407 Kernel: strings.NewReader(kernel1), 408 // Does not contain global APPEND. 409 Cmdline: "earlyprintk=ttyS0 printk=ttyS0", 410 }, 411 &boot.LinuxImage{ 412 Name: "lester", 413 Kernel: strings.NewReader(kernel1), 414 Initrd: strings.NewReader(globalInitrd), 415 // Contains only global APPEND. 416 Cmdline: "initrd=./pxefiles/global_initrd", 417 }, 418 &boot.LinuxImage{ 419 Name: "omar", 420 Kernel: strings.NewReader(kernel2), 421 // "APPEND -" means ignore global APPEND. 422 Cmdline: "", 423 }, 424 &boot.LinuxImage{ 425 Name: "stringer", 426 Kernel: strings.NewReader(kernel2), 427 Initrd: strings.NewReader(initrd2), 428 429 // TODO: See syslinux initrd handling. This SHOULD be 430 // 431 // initrd=./pxefiles/global_initrd initrd=./pxefiles/initrd2 432 // 433 // https://wiki.syslinux.org/wiki/index.php?title=Directives/append 434 Cmdline: "initrd=./pxefiles/global_initrd", 435 }, 436 }, 437 }, 438 { 439 desc: "default label does not exist", 440 configFiles: map[string]string{ 441 "/foobar/pxelinux.cfg/default": `default not-exist`, 442 }, 443 want: nil, 444 }, 445 { 446 desc: "multi-scheme valid config", 447 configFiles: map[string]string{ 448 "/foobar/pxelinux.cfg/default": ` 449 default sheeeit 450 451 label sheeeit 452 kernel ./pxefiles/kernel2 453 initrd http://someplace.com/initrd2`, 454 }, 455 want: []boot.OSImage{ 456 &boot.LinuxImage{ 457 Name: "sheeeit", 458 Kernel: strings.NewReader(kernel2), 459 Initrd: strings.NewReader(initrd2), 460 }, 461 }, 462 }, 463 { 464 desc: "valid config with three includes", 465 configFiles: map[string]string{ 466 "/foobar/pxelinux.cfg/default": ` 467 default mcnulty 468 469 include installer/txt.cfg 470 include installer/stdmenu.cfg 471 472 menu begin advanced 473 menu title Advanced Options 474 include installer/stdmenu.cfg 475 menu end 476 `, 477 478 "/foobar/installer/txt.cfg": ` 479 label mcnulty 480 kernel ./pxefiles/kernel1 481 append earlyprintk=ttyS0 printk=ttyS0 482 `, 483 484 "/foobar/installer/stdmenu.cfg": ` 485 label omar 486 kernel ./pxefiles/kernel2 487 `, 488 }, 489 want: []boot.OSImage{ 490 &boot.LinuxImage{ 491 Name: "mcnulty", 492 Kernel: strings.NewReader(kernel1), 493 Cmdline: "earlyprintk=ttyS0 printk=ttyS0", 494 }, 495 &boot.LinuxImage{ 496 Name: "omar", 497 Kernel: strings.NewReader(kernel2), 498 }, 499 }, 500 }, 501 { 502 desc: "multiboot images", 503 configFiles: map[string]string{ 504 "/foobar/pxelinux.cfg/default": ` 505 default foo 506 507 label bar 508 menu label Bla Bla Bla 509 kernel mboot.c32 510 append xen.gz console=none --- ./pxefiles/kernel1 foobar hahaha --- ./pxefiles/initrd1 511 512 label mbootnomodules 513 kernel mboot.c32 514 append xen.gz 515 516 label foo 517 linux mboot.c32 518 append earlyprintk=ttyS0 printk=ttyS0`, 519 }, 520 want: []boot.OSImage{ 521 &boot.LinuxImage{ 522 Name: "foo", 523 Kernel: strings.NewReader(mboot), 524 Cmdline: "earlyprintk=ttyS0 printk=ttyS0", 525 }, 526 &boot.MultibootImage{ 527 Name: "Bla Bla Bla", 528 Kernel: strings.NewReader(xengz), 529 Cmdline: "console=none", 530 Modules: []multiboot.Module{ 531 { 532 Module: strings.NewReader(kernel1), 533 Cmdline: "./pxefiles/kernel1 foobar hahaha", 534 }, 535 { 536 Module: strings.NewReader(initrd1), 537 Cmdline: "./pxefiles/initrd1", 538 }, 539 }, 540 }, 541 &boot.MultibootImage{ 542 Name: "mbootnomodules", 543 Kernel: strings.NewReader(xengz), 544 }, 545 }, 546 }, 547 { 548 desc: "simple config with all required file and fdt", 549 configFiles: map[string]string{ 550 "/foobar/pxelinux.cfg/default": ` 551 default foo 552 label foo 553 kernel ./pxefiles/kernel1 554 initrd ./pxefiles/global_initrd 555 append foo=bar 556 fdt ./pxefiles/board.dtb`, 557 }, 558 want: []boot.OSImage{ 559 &boot.LinuxImage{ 560 Name: "foo", 561 Kernel: strings.NewReader(kernel1), 562 Initrd: strings.NewReader(globalInitrd), 563 Cmdline: "foo=bar", 564 KexecOpts: linux.KexecOptions{}, 565 }, 566 }, 567 }, 568 } { 569 t.Run(fmt.Sprintf("Test [%02d] %s", i, tt.desc), func(t *testing.T) { 570 fs := newMockScheme() 571 for filename, content := range tt.configFiles { 572 fs.Add("1.2.3.4", filename, content) 573 } 574 s := make(curl.Schemes) 575 s.Register(fs.Scheme, fs) 576 s.Register(http.Scheme, http) 577 578 rootdir := &url.URL{ 579 Scheme: "tftp", 580 Host: "1.2.3.4", 581 Path: "/", 582 } 583 584 got, err := ParseConfigFile(context.Background(), s, "pxelinux.cfg/default", rootdir, "foobar") 585 if !reflect.DeepEqual(err, tt.err) { 586 t.Errorf("AppendFile() got %v, want %v", err, tt.err) 587 } else if err != nil { 588 return 589 } 590 591 if len(tt.want) != len(got) { 592 t.Errorf("ParseConfigFile yielded %d images, want %d images", len(got), len(tt.want)) 593 } 594 595 for i, want := range tt.want { 596 if err := boottest.SameBootImage(got[i], want); err != nil { 597 t.Errorf("Boot image index %d not same: %v", i, err) 598 } 599 } 600 }) 601 } 602 } 603 604 func TestParseCorner(t *testing.T) { 605 for _, tt := range []struct { 606 name string 607 s curl.Schemes 608 configFile string 609 rootdir *url.URL 610 wd string 611 err error 612 }{ 613 { 614 name: "no schemes", 615 s: nil, 616 configFile: "pxelinux.cfg/default", 617 rootdir: &url.URL{ 618 Scheme: "tftp", 619 Host: "1.2.3.4", 620 Path: "/foobar", 621 }, 622 err: &curl.URLError{ 623 URL: &url.URL{ 624 Scheme: "tftp", 625 Host: "1.2.3.4", 626 Path: "/foobar/pxelinux.cfg/default", 627 }, 628 Err: curl.ErrNoSuchScheme, 629 }, 630 }, 631 { 632 name: "no scheme and config file", 633 s: nil, 634 configFile: "", 635 rootdir: &url.URL{ 636 Scheme: "tftp", 637 Host: "1.2.3.4", 638 Path: "/foobar", 639 }, 640 err: &curl.URLError{ 641 URL: &url.URL{ 642 Scheme: "tftp", 643 Host: "1.2.3.4", 644 Path: "/foobar", 645 }, 646 Err: curl.ErrNoSuchScheme, 647 }, 648 }, 649 { 650 name: "no scheme, config file, and working dir", 651 s: nil, 652 configFile: "", 653 rootdir: nil, 654 err: &curl.URLError{ 655 URL: &url.URL{}, 656 Err: curl.ErrNoSuchScheme, 657 }, 658 }, 659 } { 660 t.Run(tt.name, func(t *testing.T) { 661 _, err := ParseConfigFile(context.Background(), tt.s, tt.configFile, tt.rootdir, tt.wd) 662 if !reflect.DeepEqual(err, tt.err) { 663 t.Errorf("ParseConfigFile() = %v, want %v", err, tt.err) 664 } 665 }) 666 } 667 } 668 669 func TestParseURL(t *testing.T) { 670 for _, tt := range []struct { 671 filename string 672 rootdir *url.URL 673 wd string 674 want *url.URL 675 }{ 676 { 677 filename: "foobar", 678 rootdir: mustParseURL("http://[2001::1]:18282/"), 679 wd: "files/more", 680 want: mustParseURL("http://[2001::1]:18282/files/more/foobar"), 681 }, 682 { 683 filename: "/foobar", 684 rootdir: mustParseURL("http://[2001::1]:18282"), 685 wd: "files/more", 686 want: mustParseURL("http://[2001::1]:18282/foobar"), 687 }, 688 { 689 filename: "http://[2002::2]/blabla", 690 rootdir: mustParseURL("http://[2001::1]:18282/files"), 691 wd: "more", 692 want: mustParseURL("http://[2002::2]/blabla"), 693 }, 694 { 695 filename: "http://[2002::2]/blabla", 696 rootdir: nil, 697 want: mustParseURL("http://[2002::2]/blabla"), 698 }, 699 } { 700 got, err := parseURL(tt.filename, tt.rootdir, tt.wd) 701 if err != nil { 702 t.Errorf("parseURL(%q, %s, %s) = %v, want %v", tt.filename, tt.rootdir, tt.wd, err, nil) 703 } 704 705 if !reflect.DeepEqual(got, tt.want) { 706 t.Errorf("parseURL(%q, %s, %s) = %v, want %v", tt.filename, tt.rootdir, tt.wd, got, tt.want) 707 } 708 } 709 } 710 711 func FuzzParseSyslinuxConfig(f *testing.F) { 712 713 dirPath := f.TempDir() 714 715 path := filepath.Join(dirPath, "isolinux.cfg") 716 717 log.SetOutput(io.Discard) 718 log.SetFlags(0) 719 720 // get seed corpora from testdata_new files 721 seeds, err := filepath.Glob("testdata/*/*/isolinux.cfg") 722 if err != nil { 723 f.Fatalf("failed to find seed corpora files: %v", err) 724 } 725 726 for _, seed := range seeds { 727 seedBytes, err := os.ReadFile(seed) 728 if err != nil { 729 f.Fatalf("failed read seed corpora from files %v: %v", seed, err) 730 } 731 732 f.Add(seedBytes) 733 } 734 735 f.Add([]byte("lABel 0\nAppend initrd")) 736 f.Add([]byte("lABel 0\nkernel mboot.c32\nAppend ---")) 737 f.Fuzz(func(t *testing.T, data []byte) { 738 739 if len(data) > 4096 { 740 return 741 } 742 743 // do not allow arbitrary files reads 744 if bytes.Contains(data, []byte("include")) { 745 return 746 } 747 748 err := os.WriteFile(path, data, 0o777) 749 if err != nil { 750 t.Fatalf("Failed to create configfile '%v':%v", path, err) 751 } 752 753 ParseLocalConfig(context.Background(), dirPath) 754 }) 755 756 }