/** * Deallocate space on the real-mode stack, optionally copying back * data to a user buffer. * * @v data User buffer * @v size Size of stack data */ void remove_user_from_rm_stack ( userptr_t data, size_t size ) { if ( data ) { userptr_t rm_stack = real_to_user ( rm_ss, rm_sp ); memcpy_user ( rm_stack, 0, data, 0, size ); } rm_sp += size; };
/** * Allocate space on the real-mode stack and copy data there from a * user buffer * * @v data User buffer * @v size Size of stack data * @ret sp New value of real-mode stack pointer */ uint16_t copy_user_to_rm_stack ( userptr_t data, size_t size ) { userptr_t rm_stack; rm_sp -= size; rm_stack = real_to_user ( rm_ss, rm_sp ); memcpy_user ( rm_stack, 0, data, 0, size ); return rm_sp; };
/** * Initialise initrd * * @ret rc Return status code */ static int initrd_init ( void ) { struct image *image; int rc; /* Do nothing if no initrd was specified */ if ( ! initrd_phys ) { DBGC ( colour, "RUNTIME found no initrd\n" ); return 0; } if ( ! initrd_len ) { DBGC ( colour, "RUNTIME found empty initrd\n" ); return 0; } DBGC ( colour, "RUNTIME found initrd at [%x,%x)\n", initrd_phys, ( initrd_phys + initrd_len ) ); /* Allocate image */ image = alloc_image(); if ( ! image ) { DBGC ( colour, "RUNTIME could not allocate image for " "initrd\n" ); rc = -ENOMEM; goto err_alloc_image; } image_set_name ( image, "<INITRD>" ); /* Allocate and copy initrd content */ image->data = umalloc ( initrd_len ); if ( ! image->data ) { DBGC ( colour, "RUNTIME could not allocate %zd bytes for " "initrd\n", initrd_len ); rc = -ENOMEM; goto err_umalloc; } image->len = initrd_len; memcpy_user ( image->data, 0, phys_to_user ( initrd_phys ), 0, initrd_len ); /* Mark initrd as consumed */ initrd_phys = 0; /* Register image */ if ( ( rc = register_image ( image ) ) != 0 ) { DBGC ( colour, "RUNTIME could not register initrd: %s\n", strerror ( rc ) ); goto err_register_image; } /* Drop our reference to the image */ image_put ( image ); return 0; err_register_image: err_umalloc: image_put ( image ); err_alloc_image: return rc; }
/** * Reallocate external memory * * @v old_ptr Memory previously allocated by umalloc(), or UNULL * @v new_size Requested size * @ret new_ptr Allocated memory, or UNULL * * Calling realloc() with a new size of zero is a valid way to free a * memory block. */ static userptr_t efi_urealloc ( userptr_t old_ptr, size_t new_size ) { EFI_BOOT_SERVICES *bs = efi_systab->BootServices; EFI_PHYSICAL_ADDRESS phys_addr; unsigned int new_pages, old_pages; userptr_t new_ptr = UNOWHERE; size_t old_size; EFI_STATUS efirc; int rc; /* Allocate new memory if necessary. If allocation fails, * return without touching the old block. */ if ( new_size ) { new_pages = ( EFI_SIZE_TO_PAGES ( new_size ) + 1 ); if ( ( efirc = bs->AllocatePages ( AllocateAnyPages, EfiBootServicesData, new_pages, &phys_addr ) ) != 0 ) { rc = -EEFI ( efirc ); DBG ( "EFI could not allocate %d pages: %s\n", new_pages, strerror ( rc ) ); return UNULL; } assert ( phys_addr != 0 ); new_ptr = phys_to_user ( phys_addr + EFI_PAGE_SIZE ); copy_to_user ( new_ptr, -EFI_PAGE_SIZE, &new_size, sizeof ( new_size ) ); DBG ( "EFI allocated %d pages at %llx\n", new_pages, phys_addr ); } /* Copy across relevant part of the old data region (if any), * then free it. Note that at this point either (a) new_ptr * is valid, or (b) new_size is 0; either way, the memcpy() is * valid. */ if ( old_ptr && ( old_ptr != UNOWHERE ) ) { copy_from_user ( &old_size, old_ptr, -EFI_PAGE_SIZE, sizeof ( old_size ) ); memcpy_user ( new_ptr, 0, old_ptr, 0, ( (old_size < new_size) ? old_size : new_size )); old_pages = ( EFI_SIZE_TO_PAGES ( old_size ) + 1 ); phys_addr = user_to_phys ( old_ptr, -EFI_PAGE_SIZE ); if ( ( efirc = bs->FreePages ( phys_addr, old_pages ) ) != 0 ){ rc = -EEFI ( efirc ); DBG ( "EFI could not free %d pages at %llx: %s\n", old_pages, phys_addr, strerror ( rc ) ); /* Not fatal; we have leaked memory but successfully * allocated (if asked to do so). */ } DBG ( "EFI freed %d pages at %llx\n", old_pages, phys_addr ); } return new_ptr; }
/** * Execute PXE image * * @v image PXE image * @ret rc Return status code */ static int pxe_exec ( struct image *image ) { userptr_t buffer = real_to_user ( 0, 0x7c00 ); struct net_device *netdev; int rc; /* Verify and prepare segment */ if ( ( rc = prep_segment ( buffer, image->len, image->len ) ) != 0 ) { DBGC ( image, "IMAGE %p could not prepare segment: %s\n", image, strerror ( rc ) ); return rc; } /* Copy image to segment */ memcpy_user ( buffer, 0, image->data, 0, image->len ); /* Arbitrarily pick the most recently opened network device */ if ( ( netdev = last_opened_netdev() ) == NULL ) { DBGC ( image, "IMAGE %p could not locate PXE net device\n", image ); return -ENODEV; } netdev_get ( netdev ); /* Activate PXE */ pxe_activate ( netdev ); /* Construct fake DHCP packets */ pxe_fake_cached_info(); /* Set PXE command line */ pxe_cmdline = image->cmdline; /* Reset console since PXE NBP will probably use it */ console_reset(); /* Disable IRQ, if applicable */ if ( netdev_irq_supported ( netdev ) && netdev->dev->desc.irq ) disable_irq ( netdev->dev->desc.irq ); /* Start PXE NBP */ rc = pxe_start_nbp(); /* Clear PXE command line */ pxe_cmdline = NULL; /* Deactivate PXE */ pxe_deactivate(); /* Try to reopen network device. Ignore errors, since the NBP * may have called PXENV_STOP_UNDI. */ netdev_open ( netdev ); netdev_put ( netdev ); return rc; }
/** * Load ELF segment into memory * * @v image ELF file * @v phdr ELF program header * @ret rc Return status code */ static int elf_load_segment ( struct image *image, Elf_Phdr *phdr ) { physaddr_t dest; userptr_t buffer; int rc; /* Do nothing for non-PT_LOAD segments */ if ( phdr->p_type != PT_LOAD ) return 0; /* Check segment lies within image */ if ( ( phdr->p_offset + phdr->p_filesz ) > image->len ) { DBG ( "ELF segment outside ELF file\n" ); return -ENOEXEC; } /* Find start address: use physical address for preference, * fall back to virtual address if no physical address * supplied. */ dest = phdr->p_paddr; if ( ! dest ) dest = phdr->p_vaddr; if ( ! dest ) { DBG ( "ELF segment loads to physical address 0\n" ); return -ENOEXEC; } buffer = phys_to_user ( dest ); DBG ( "ELF loading segment [%x,%x) to [%x,%x,%x)\n", phdr->p_offset, ( phdr->p_offset + phdr->p_filesz ), phdr->p_paddr, ( phdr->p_paddr + phdr->p_filesz ), ( phdr->p_paddr + phdr->p_memsz ) ); /* Verify and prepare segment */ if ( ( rc = prep_segment ( buffer, phdr->p_filesz, phdr->p_memsz ) ) != 0 ) { DBG ( "ELF could not prepare segment: %s\n", strerror ( rc ) ); return rc; } /* Copy image to segment */ memcpy_user ( buffer, 0, image->data, phdr->p_offset, phdr->p_filesz ); return 0; }
/** * Load PXE image into memory * * @v image PXE file * @ret rc Return status code */ int pxe_load ( struct image *image ) { userptr_t buffer = real_to_user ( 0, 0x7c00 ); size_t filesz = image->len; size_t memsz = image->len; int rc; /* Images too large to fit in base memory cannot be PXE * images. We include this check to help prevent unrecognised * images from being marked as PXE images, since PXE images * have no signature we can check against. */ if ( filesz > ( 0xa0000 - 0x7c00 ) ) return -ENOEXEC; /* Rejecting zero-length images is also useful, since these * end up looking to the user like bugs in gPXE. */ if ( ! filesz ) return -ENOEXEC; /* There are no signature checks for PXE; we will accept anything */ if ( ! image->type ) image->type = &pxe_image_type; /* Verify and prepare segment */ if ( ( rc = prep_segment ( buffer, filesz, memsz ) ) != 0 ) { DBGC ( image, "IMAGE %p could not prepare segment: %s\n", image, strerror ( rc ) ); return rc; } /* Copy image to segment */ memcpy_user ( buffer, 0, image->data, 0, filesz ); return 0; }
/** * Load ELF segment into memory * * @v image ELF file * @v phdr ELF program header * @v ehdr ELF executable header * @ret entry Entry point, if found * @ret max Maximum used address * @ret rc Return status code */ static int elf_load_segment ( struct image *image, Elf_Phdr *phdr, Elf_Ehdr *ehdr, physaddr_t *entry, physaddr_t *max ) { physaddr_t dest; physaddr_t end; userptr_t buffer; unsigned long e_offset; int rc; /* Do nothing for non-PT_LOAD segments */ if ( phdr->p_type != PT_LOAD ) return 0; /* Check segment lies within image */ if ( ( phdr->p_offset + phdr->p_filesz ) > image->len ) { DBGC ( image, "ELF %p segment outside image\n", image ); return -ENOEXEC; } /* Find start address: use physical address for preference, * fall back to virtual address if no physical address * supplied. */ dest = phdr->p_paddr; if ( ! dest ) dest = phdr->p_vaddr; if ( ! dest ) { DBGC ( image, "ELF %p segment loads to physical address 0\n", image ); return -ENOEXEC; } buffer = phys_to_user ( dest ); end = ( dest + phdr->p_memsz ); DBGC ( image, "ELF %p loading segment [%x,%x) to [%x,%x,%x)\n", image, phdr->p_offset, ( phdr->p_offset + phdr->p_filesz ), phdr->p_paddr, ( phdr->p_paddr + phdr->p_filesz ), ( phdr->p_paddr + phdr->p_memsz ) ); /* Verify and prepare segment */ if ( ( rc = prep_segment ( buffer, phdr->p_filesz, phdr->p_memsz ) ) != 0 ) { DBGC ( image, "ELF %p could not prepare segment: %s\n", image, strerror ( rc ) ); return rc; } /* Update maximum used address, if applicable */ if ( end > *max ) *max = end; /* Copy image to segment */ memcpy_user ( buffer, 0, image->data, phdr->p_offset, phdr->p_filesz ); /* Set execution address, if it lies within this segment */ if ( ( e_offset = ( ehdr->e_entry - dest ) ) < phdr->p_filesz ) { *entry = ehdr->e_entry; DBGC ( image, "ELF %p found physical entry point at %lx\n", image, *entry ); } else if ( ( e_offset = ( ehdr->e_entry - phdr->p_vaddr ) ) < phdr->p_filesz ) { if ( ! *entry ) { *entry = ( dest + e_offset ); DBGC ( image, "ELF %p found virtual entry point at %lx" " (virt %lx)\n", image, *entry, ( ( unsigned long ) ehdr->e_entry ) ); } } return 0; }
/** * Read and verify El Torito Boot Record Volume Descriptor * * @v image El Torito file * @ret catalog_offset Offset of Boot Catalog * @ret rc Return status code */ static int eltorito_read_voldesc ( struct image *image, unsigned long *catalog_offset ) { static const struct eltorito_vol_desc vol_desc_signature = { .record_indicator = 0, .iso9660_id = "CD001", .version = 1, .system_indicator = "EL TORITO SPECIFICATION", }; struct eltorito_vol_desc vol_desc; /* Sanity check */ if ( image->len < ( ELTORITO_VOL_DESC_OFFSET + ISO9660_BLKSIZE ) ) { DBGC ( image, "ElTorito %p too short\n", image ); return -ENOEXEC; } /* Read and verify Boot Record Volume Descriptor */ copy_from_user ( &vol_desc, image->data, ELTORITO_VOL_DESC_OFFSET, sizeof ( vol_desc ) ); if ( memcmp ( &vol_desc, &vol_desc_signature, offsetof ( typeof ( vol_desc ), sector ) ) != 0 ) { DBGC ( image, "ElTorito %p invalid Boot Record Volume " "Descriptor\n", image ); return -ENOEXEC; } *catalog_offset = ( vol_desc.sector * ISO9660_BLKSIZE ); DBGC ( image, "ElTorito %p boot catalog at offset %#lx\n", image, *catalog_offset ); return 0; } /** * Read and verify El Torito Boot Catalog * * @v image El Torito file * @v catalog_offset Offset of Boot Catalog * @ret boot_entry El Torito boot entry * @ret rc Return status code */ static int eltorito_read_catalog ( struct image *image, unsigned long catalog_offset, struct eltorito_boot_entry *boot_entry ) { struct eltorito_validation_entry validation_entry; /* Sanity check */ if ( image->len < ( catalog_offset + ISO9660_BLKSIZE ) ) { DBGC ( image, "ElTorito %p bad boot catalog offset %#lx\n", image, catalog_offset ); return -ENOEXEC; } /* Read and verify the Validation Entry of the Boot Catalog */ copy_from_user ( &validation_entry, image->data, catalog_offset, sizeof ( validation_entry ) ); if ( word_checksum ( &validation_entry, sizeof ( validation_entry ) ) != 0 ) { DBGC ( image, "ElTorito %p bad Validation Entry checksum\n", image ); return -ENOEXEC; } /* Read and verify the Initial/Default entry */ copy_from_user ( boot_entry, image->data, ( catalog_offset + sizeof ( validation_entry ) ), sizeof ( *boot_entry ) ); if ( boot_entry->indicator != ELTORITO_BOOTABLE ) { DBGC ( image, "ElTorito %p not bootable\n", image ); return -ENOEXEC; } if ( boot_entry->media_type != ELTORITO_NO_EMULATION ) { DBGC ( image, "ElTorito %p cannot support media type %d\n", image, boot_entry->media_type ); return -ENOTSUP; } DBGC ( image, "ElTorito %p media type %d segment %04x\n", image, boot_entry->media_type, boot_entry->load_segment ); return 0; } /** * Load El Torito virtual disk image into memory * * @v image El Torito file * @v boot_entry El Torito boot entry * @ret rc Return status code */ static int eltorito_load_disk ( struct image *image, struct eltorito_boot_entry *boot_entry ) { unsigned long start = ( boot_entry->start * ISO9660_BLKSIZE ); unsigned long length = ( boot_entry->length * ISO9660_BLKSIZE ); unsigned int load_segment; userptr_t buffer; int rc; /* Sanity check */ if ( image->len < ( start + length ) ) { DBGC ( image, "ElTorito %p virtual disk lies outside image\n", image ); return -ENOEXEC; } DBGC ( image, "ElTorito %p virtual disk at %#lx+%#lx\n", image, start, length ); /* Calculate load address */ load_segment = boot_entry->load_segment; buffer = real_to_user ( load_segment, ( load_segment ? 0 : 0x7c00 ) ); /* Verify and prepare segment */ if ( ( rc = prep_segment ( buffer, length, length ) ) != 0 ) { DBGC ( image, "ElTorito %p could not prepare segment: %s\n", image, strerror ( rc ) ); return rc; } /* Copy image to segment */ memcpy_user ( buffer, 0, image->data, start, length ); return 0; }