diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 85c62e7bf..608d266d4 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -81,6 +81,10 @@ jobs: - name: Create disk image run: cargo make disk + - name: Create secure disk image (Linux-only) + run: cargo make disk-secure + if: steps.isUbuntu.outputs.result + - name: Test run: cargo make test if: steps.isUbuntu.outputs.result diff --git a/.gitignore b/.gitignore index 224eb662c..5c9514ce8 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ target **/*.rs.bk Cargo.lock -isofiles/boot/sunrise-* os.iso DISK.img .idea diff --git a/Cargo.lock b/Cargo.lock index 6c5681233..12c63ce2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -98,6 +98,16 @@ dependencies = [ "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", ] +[[package]] +name = "bincode" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "bit-set" version = "0.5.1" @@ -724,6 +734,17 @@ dependencies = [ "pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "gen-secureboot-keys" +version = "0.1.0" +dependencies = [ + "bincode 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "generational-arena" version = "0.2.3" @@ -1416,6 +1437,14 @@ dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "proc-macro2" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "ptx" version = "0.0.1" @@ -1455,6 +1484,14 @@ dependencies = [ "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "quote" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rand" version = "0.3.23" @@ -1719,6 +1756,21 @@ dependencies = [ "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", ] +[[package]] +name = "serde" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde_derive" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "sha-1" version = "0.8.1" @@ -2094,10 +2146,22 @@ dependencies = [ "sunrise-libutils 0.1.0", ] +[[package]] +name = "sunrise-twili" +version = "0.1.0" +dependencies = [ + "core-futures-tls 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "sunrise-libuser 0.1.0", +] + [[package]] name = "sunrise-vi" version = "0.1.0" dependencies = [ + "bit_field 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "core-futures-tls 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "font-rs 0.1.3 (git+https://github.com/SunriseOS/font-rs)", "futures-preview 0.3.0-alpha.16 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2151,6 +2215,16 @@ dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "syn" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "sync" version = "0.0.1" @@ -2415,6 +2489,11 @@ name = "unicode-xid" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "unindent" version = "0.1.5" @@ -2743,6 +2822,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum atomic 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "c210c1f4db048cda477b652d170572d84c9640695835f17663595d3bd543fc28" "checksum atty 0.2.13 (git+https://github.com/sunriseos/atty.git)" = "" "checksum autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "22130e92352b948e7e82a49cdb0aa94f2211761117f29e052dd397c1ac33542b" +"checksum bincode 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8ab639324e3ee8774d296864fbc0dbbb256cf1a41c490b94cba90c082915f92" "checksum bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e84c238982c4b1e1ee668d136c510c67a13465279c0cb367ea6baf6310620a80" "checksum bit-vec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f59bbe95d4e52a6398ec21238d31577f2b28a9d86807f06ca59d191d8440d0bb" "checksum bit_field 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a165d606cf084741d4ac3a28fb6e9b1eb0bd31f6cd999098cfddb0b2ab381dc0" @@ -2850,9 +2930,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum proc-macro-hack 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "463bf29e7f11344e58c9e01f171470ab15c925c6822ad75028cc1c0e1d1eb63b" "checksum proc-macro-hack-impl 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "38c47dcb1594802de8c02f3b899e2018c78291168a22c281be21ea0fb4796842" "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +"checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27" "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" "checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" "checksum rand 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)" = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" "checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" "checksum rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9" @@ -2878,6 +2960,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "585e8ddcedc187886a30fa705c47985c3fa88d06624095856b36ca0b82ff4421" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +"checksum serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "9796c9b7ba2ffe7a9ce53c2287dfc48080f4b2b362fcc245a259b3a7201119dd" +"checksum serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "4b133a43a1ecd55d4086bd5b4dc6c1751c68b1bfbeba7a5040442022c7e7c02e" "checksum sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "23962131a91661d643c98940b20fcaffe62d776a823247be80a48fcb8b6fce68" "checksum sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" "checksum sha2 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7d963c78ce367df26d7ea8b8cc655c651b42e8a1e584e869c1e17dae3ccb116a" @@ -2892,6 +2976,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum structopt 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "16c2cdbf9cc375f15d1b4141bc48aeef444806655cd0e904207edc8d68d86ed7" "checksum structopt-derive 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "53010261a84b37689f9ed7d395165029f9cc7abb9f56bbfe86bee2597ed25107" "checksum syn 0.15.40 (registry+https://github.com/rust-lang/crates.io-index)" = "bc945221ccf4a7e8c31222b9d1fc77aefdd6638eb901a6ce457a3dc29d4c31e8" +"checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf" "checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" "checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" "checksum tempfile 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "11ce2fe9db64b842314052e2421ac61a73ce41b898dc8e3750398b219c5fc1e0" @@ -2909,6 +2994,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" "checksum unindent 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "63f18aa3b0e35fed5a0048f029558b1518095ffe2a0a31fb87c93dece93a4993" "checksum unix_socket 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6aa2700417c405c38f5e6902d699345241c28c0b7ade4abaad71e35a87eb1564" "checksum uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)" = "" diff --git a/Cargo.toml b/Cargo.toml index 07647c576..101092a2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,8 @@ [workspace] -members = ["kernel", "bootstrap", "shell", "time", "libuser", "wall-clock", "sm", "vi", "ahci", "fs", "libutils", "libkern", "swipc-gen", "swipc-parser", "docs", "libtimezone", "disk-initializer", "loader", "keyboard", "std_hello_world", "coreutils"] +members = ["kernel", "bootstrap", "shell", "time", "libuser", "wall-clock", + "sm", "vi", "ahci", "fs", "libutils", "libkern", "swipc-gen", + "swipc-parser", "docs", "libtimezone", "disk-initializer", "loader", + "keyboard", "std_hello_world", "twili", "coreutils", "gen-secureboot-keys"] [patch.crates-io.libc] git = "https://github.com/sunriseos/libc.git" diff --git a/Makefile.toml b/Makefile.toml index 000a5dd6c..dd70cc5af 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -33,8 +33,8 @@ QEMU_COMMON_FLAGS = """\ -no-reboot \ -drive id=diskA,file=DISK.img,format=raw,if=none -device ahci,id=ahci \ -device ide-drive,drive=diskA,bus=ahci.0 \ - -machine q35 \ - -m 512M""" + -machine q35,smm=on, \ + -m 512""" # 512MiB ram is required for UEFI boot. Go figure... #### Profile-specific flags # Which subfolder of target will rustc put its files into. Target is @@ -77,6 +77,39 @@ install_crate = { crate_name = "mkisofs-rs", binary = "mkisofs-rs", test_arg = " dependencies = ["install-rust-src"] install_crate = { crate_name = "xargo", binary = "xargo", test_arg = "--version", min_version = "0.3.16" } +[tasks.gen-secureboot-keys] +# https://wiki.gentoo.org/wiki/Sakaki%27s_EFI_Install_Guide/Configuring_Secure_Boot +# This link is the most useful stuff I could find. +# We're going to use the same key for the PK, the KEK and the DB. No reason to +# make an overcomplicated setup. +condition = { platforms = ["linux"] } +script = [''' +mkdir -p target/keys +# Generate the Secure Boot Key. +# TODO: Integrate secureboot key generation into setup-ovmf. +# BODY: Secure boot keygen depends on openssl, efitools and sed - none of which +# BODY: are available on windows. We should integrate it all into the +# BODY: gen-secureboot-keys tool. +openssl req -new -x509 -newkey rsa:2048 -subj "/CN=SecureBoot Platform Key/" -outform PEM -keyout target/keys/PK.key -out target/keys/PK.crt -nodes -sha256 + +# Random UUID chosen by fair dice roll. +cert-to-efi-sig-list -g "6270c1f5-7f36-4239-8be0-b617d586e07e" target/keys/PK.crt target/keys/PK.esl + +# Not a random UUID. This is used by OVMF to detect the PK.crt in the SMBIOS +# type 11 strings. +sed \ + -e 's/^-----BEGIN CERTIFICATE-----$/4e32566d-8e9e-4f52-81d3-5bb9715f9727:/' \ + -e '/^-----END CERTIFICATE-----$/d' \ + target/keys/PK.oemstr +'''] + +[tasks.setup-ovmf] +condition = { platforms = ["linux"] } +dependencies = ["gen-secureboot-keys"] +command = "cargo" +args = ["run", "-p", "gen-secureboot-keys"] + [tasks.bootstrap] description = "Compiles the i386 bootstrap" dependencies = ["bootstrap-linker", "install-xargo"] @@ -143,6 +176,12 @@ dependencies = ["install-xargo"] command = "xargo" args = ["build", "--target=i386-unknown-sunrise-user", "--package=sunrise-keyboard", "@@split(COMPILER_FLAGS, )"] +[tasks.twili] +description = "Compiles sunrise-twili" +dependencies = ["install-xargo"] +command = "xargo" +args = ["build", "--target=i386-unknown-sunrise-user", "--package=sunrise-twili", "@@split(COMPILER_FLAGS, )"] + [tasks.std_hello_world] description = "Compiles std_hello_world" dependencies = ["install-xargo"] @@ -155,63 +194,128 @@ dependencies = ["install-xargo"] command = "xargo" args = ["build", "--target=i386-unknown-sunrise-user", "--package=uutils", "-Z", "package-features", "--features=sunrise", "--no-default-features", "@@split(COMPILER_FLAGS, )"] +[tasks.userspace-nostd] +internal = true +command = "xargo" +args = ["build", "--target=i386-unknown-sunrise-user", "@@split(COMPILER_FLAGS, )", + "-p", "sunrise-shell", "-p", "sunrise-wall-clock", "-p", "sunrise-sm", + "-p", "sunrise-vi", "-p", "sunrise-ahci", "-p", "sunrise-time", + "-p", "sunrise-fs", "-p", "sunrise-loader", "-p", "sunrise-keyboard", + "-p", "sunrise-twili" +] + [tasks.userspace] description = "Compiles userspace apps" -dependencies = ["shell", "wall-clock", "sm", "vi", "ahci", "time", "fs", "loader", "keyboard", "std_hello_world", "uutils"] +dependencies = ["userspace-nostd", "std_hello_world", "uutils"] + +[tasks.iso-common] +private = true +description = "Copies the KIPs to the isofiles folder." +dependencies = ["bootstrap", "kernel", "userspace"] +script_runner = "@shell" +script = [''' +mkdir -p target/isofiles/boot +cp target/i386-unknown-none/$PROFILE_NAME/sunrise-bootstrap target/isofiles/boot/ +cp target/i386-unknown-none/$PROFILE_NAME/sunrise-kernel target/isofiles/boot/ +cp target/i386-unknown-sunrise-user/$PROFILE_NAME/sunrise-shell target/isofiles/boot/ +cp target/i386-unknown-sunrise-user/$PROFILE_NAME/sunrise-time target/isofiles/boot/ +cp target/i386-unknown-sunrise-user/$PROFILE_NAME/sunrise-wall-clock target/isofiles/boot/ +cp target/i386-unknown-sunrise-user/$PROFILE_NAME/sunrise-sm target/isofiles/boot/ +cp target/i386-unknown-sunrise-user/$PROFILE_NAME/sunrise-vi target/isofiles/boot/ +cp target/i386-unknown-sunrise-user/$PROFILE_NAME/sunrise-ahci target/isofiles/boot/ +cp target/i386-unknown-sunrise-user/$PROFILE_NAME/sunrise-fs target/isofiles/boot/ +'''] [tasks.iso] description = "Creates a bootable ISO containing the kernel and grub." -dependencies = ["bootstrap", "kernel", "userspace", "install-mkisofs-rs"] +dependencies = ["iso-common", "install-mkisofs-rs"] script_runner = "@shell" script = [ ''' -cp target/i386-unknown-none/$PROFILE_NAME/sunrise-bootstrap isofiles/boot/ -cp target/i386-unknown-none/$PROFILE_NAME/sunrise-kernel isofiles/boot/ -cp target/i386-unknown-sunrise-user/$PROFILE_NAME/sunrise-shell isofiles/boot/ -cp target/i386-unknown-sunrise-user/$PROFILE_NAME/sunrise-time isofiles/boot/ -cp target/i386-unknown-sunrise-user/$PROFILE_NAME/sunrise-wall-clock isofiles/boot/ -cp target/i386-unknown-sunrise-user/$PROFILE_NAME/sunrise-sm isofiles/boot/ -cp target/i386-unknown-sunrise-user/$PROFILE_NAME/sunrise-vi isofiles/boot/ -cp target/i386-unknown-sunrise-user/$PROFILE_NAME/sunrise-ahci isofiles/boot/ -cp target/i386-unknown-sunrise-user/$PROFILE_NAME/sunrise-fs isofiles/boot/ -cp target/i386-unknown-sunrise-user/$PROFILE_NAME/sunrise-loader isofiles/boot/ -cp target/i386-unknown-sunrise-user/$PROFILE_NAME/sunrise-keyboard isofiles/boot/ -mkisofs-rs external/grub/isofiles isofiles -o os.iso -b boot/grub/i386-pc/eltorito.img --no-emul-boot --boot-info-table --embedded-boot external/grub/embedded.img +mkisofs-rs external/grub/isofiles isofiles target/isofiles/ -o os.iso -b boot/grub/i386-pc/eltorito.img --no-emul-boot --boot-info-table --embedded-boot external/grub/embedded.img +'''] + +[tasks.iso-secure] +description = "Generate a secure GRUB UEFI image, and creates a bootable ISO with it." +dependencies = ["iso-common", "gen-secureboot-keys", "install-mkisofs-rs"] +script = [''' +# Sign GRUB configuration and kernel with a fresh key. +TEMPDIR=$(mktemp -d) +TEMPFILE=$(mktemp) +cat >$TEMPFILE < ! { // Move the multiboot_header to a single page in kernel space. This simplifies some // things in the kernel. - let multiboot_info_page = page_tables.get_page::(); + let multiboot_page_nb = sunrise_libutils::div_ceil(boot_info.total_size(), PAGE_SIZE); + let multiboot_info_page = page_tables.find_available_virtual_space::(multiboot_page_nb).unwrap(); + page_tables.map_range_allocate(multiboot_info_page, multiboot_page_nb, EntryFlags::WRITABLE); let multiboot_phys_page = page_tables.get_phys(multiboot_info_page).unwrap(); let total_size = boot_info.total_size(); - assert!(total_size <= paging::PAGE_SIZE, "Expected multiboot info to fit in a page"); unsafe { // Safety: We just allocated this page. What could go wrong? core::ptr::copy(multiboot_info_addr as *const u8, diff --git a/disk-initializer/src/main.rs b/disk-initializer/src/main.rs index 8abc294f0..15abaae77 100644 --- a/disk-initializer/src/main.rs +++ b/disk-initializer/src/main.rs @@ -18,7 +18,7 @@ use storage_device::storage_device::{StorageDevice, StorageBlockDevice}; use libfat; use libfat::FatFileSystemResult; use libfat::filesystem::FatFileSystem; -use libfat::FatFsType; +use libfat::{FatError, FatFsType}; use libfat::directory::File as FatFile; mod gpt; @@ -62,12 +62,24 @@ fn write_tempate_to_filesystem(filesystem: &FatFileSystem, dir: &Path, fil let filesystem_entry_path_str = filesystem_entry_path.to_str().unwrap(); if path.is_dir() { - filesystem.create_directory(filesystem_entry_path_str).expect("Cannot create directory in the filesystem"); + let err = filesystem.create_directory(filesystem_entry_path_str); + match err { + Ok(()) | Err(FatError::FileExists) => (), + Err(err) => Err(err).expect("Cannot create directory in the filesystem") + } write_tempate_to_filesystem(filesystem, &path, &mut filesystem_entry_path)?; } else { - filesystem.create_file(filesystem_entry_path_str).expect("Cannot create file in the filesystem"); - let file = filesystem.open_file(filesystem_entry_path_str).expect("Cannot open file in the filesystem"); - write_file_to_filesystem(filesystem, file, path.to_str().unwrap()).expect("Cannot write file to filesystem"); + match filesystem.create_file(filesystem_entry_path_str) { + Ok(()) => { + let file = filesystem.open_file(filesystem_entry_path_str).expect("Cannot open file in the filesystem"); + write_file_to_filesystem(filesystem, file, path.to_str().unwrap()).expect("Cannot write file to filesystem"); + }, + Err(FatError::FileExists) => { + println!("{} was already handled previously. Ignoring.", path.display()); + continue; + }, + Err(err) => Err(err).expect("Cannot create file in the filesystem") + } } } } @@ -84,7 +96,6 @@ fn main() { let file_name = env::args().nth(1).expect("File name is expected"); let file_size = env::args().nth(2).expect("Disk size is expected"); - let template_path = env::args().nth(3).expect("Template path is expected"); let file_size = u64::from_str_radix(file_size.as_str(), 10).expect("Cannot parse file size"); let file = OpenOptions::new().create(true).read(true).write(true).open(file_name.clone()).expect("Cannot create file"); @@ -126,5 +137,7 @@ fn main() { let mut filesystem_path = PathBuf::new(); filesystem_path.push("/"); - write_tempate_to_filesystem(&filesystem, Path::new(&template_path), &mut filesystem_path).expect("Failed to write template to filesystem"); + for template_path in env::args().skip(3) { + write_tempate_to_filesystem(&filesystem, Path::new(&template_path), &mut filesystem_path).expect("Failed to write template to filesystem"); + } } diff --git a/docs/BUILDING.md b/docs/BUILDING.md index 597444764..81b71ee9c 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -43,6 +43,15 @@ server on VNC port 5900 (overridable by setting the `VNC_PORT` environment variable) through which the user can interact. Logs going over serial port will be printed on stdout. +## Secure Boot + +It's possible to build a secureboot-secured image. For this, you'll need to +install OpenSSL and GnuPG (See the [version sections](#versions)). Secure Boot +currently only works by generating a FAT disk image that can be flashed on a +hard drive to boot from. In order to generate such a disk, run +`cargo make disk-secure`. In order to run the image in qemu with secure boot +enabled, run `cargo make qemu-secure`. + ## Versions - rust: `nightly-2019-15-07` @@ -51,5 +60,7 @@ printed on stdout. - cargo-make: `0.22.0` - xargo: `0.3.16 (21e4808)` - mkisofs-rs: `0.1.1` -- qemu-system-i386: `4.0.50` -- cargo-travis: `https://github.com/roblabla/cargo-travis` branch `doc-upload-target` +- qemu-system-i386 (for qemu only): `4.0.50` +- cargo-travis (for CI only): `https://github.com/roblabla/cargo-travis` branch `doc-upload-target` +- OpenSSL (for secure boot only): `1.1.1c` +- GnuPG (for secure boot only): `2.2.17` \ No newline at end of file diff --git a/external/embedded_grub.cfg b/external/embedded_grub.cfg new file mode 100644 index 000000000..93a50b358 --- /dev/null +++ b/external/embedded_grub.cfg @@ -0,0 +1,4 @@ +set superusers="root" +set signatures=enforce +set root=(ahci0,gpt1) +normal /boot/grub/grub.cfg \ No newline at end of file diff --git a/external/ovmf/OVMF_CODE-pure-efi.fd b/external/ovmf/OVMF_CODE-pure-efi.fd new file mode 100644 index 000000000..fb6db3a8e Binary files /dev/null and b/external/ovmf/OVMF_CODE-pure-efi.fd differ diff --git a/gen-secureboot-keys/Cargo.toml b/gen-secureboot-keys/Cargo.toml new file mode 100644 index 000000000..8d80ae535 --- /dev/null +++ b/gen-secureboot-keys/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "gen-secureboot-keys" +version = "0.1.0" +authors = ["roblabla "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bincode = "1.1.4" +serde = "1.0" +serde_derive = "1.0" +chrono = "0.4" +bitflags = "1.0" \ No newline at end of file diff --git a/gen-secureboot-keys/src/main.rs b/gen-secureboot-keys/src/main.rs new file mode 100644 index 000000000..7df794cbd --- /dev/null +++ b/gen-secureboot-keys/src/main.rs @@ -0,0 +1,354 @@ +//! SecureBoot Key Generator +//! +//! Generates an NVRAM for the UEFI configured for the given PK/KEK/DB key. To +//! keep key management simple, the same key is used for all three purposes. +//! +//! This is meant to be used with the OVMF Firmware UEFI firmware. +//! +//! UEFI Specification: https://web.archive.org/web/20190822022034/https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_final.pdf +//! +//! TianoCore commit: https://github.com/tianocore/edk2/tree/e18d1c37e812284c5db1f2775db15ca349730138 +#![feature(const_int_conversion)] + +use std::io::{Seek, SeekFrom, Write}; +use std::fs::File; +use std::convert::TryFrom; +use std::mem::size_of; + +use serde_derive::Serialize; +use chrono::prelude::*; +use bitflags::bitflags; + +/// A Globally Unique Identifier. Everything that requires an ID in the EFI spec +/// uses a GUID to avoid collisions and whatnot. +#[derive(Clone, Copy, Serialize)] +struct EfiGuid { + /// The data of the GUID, as a byte array. + data: [u8; 16] +} + +impl EfiGuid { + /// Creates a new GUID from the subcomponent. + const fn new(first: u32, second: u16, third: u16, rest: [u8; 8]) -> EfiGuid { + let f = first.to_le_bytes(); + let s = second.to_le_bytes(); + let t = third.to_le_bytes(); + let r = rest; + EfiGuid { + data: [f[0], f[1], f[2], f[3], s[0], s[1], t[0], t[1], + r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7]] + } + } + + /// GUID for the VendorKeysNv variable. + /// + /// [Taken from TianoCore](https://github.com/tianocore/edk2/blob/e18d1c37e812284c5db1f2775db15ca349730138/SecurityPkg/SecurityPkg.dec#L102) + const EFI_VENDOR_KEYS_NV: EfiGuid = + EfiGuid::new(0x9073e4e0, 0x60ec, 0x4b6e, [0x99, 0x03, 0x4c, 0x22, 0x3c, 0x26, 0x0f, 0x3c]); + /// GUID for the SecureBootEnable variable. + /// + /// [Taken from TianoCore](https://github.com/tianocore/edk2/blob/e18d1c37e812284c5db1f2775db15ca349730138/SecurityPkg/SecurityPkg.dec#L87) + const EFI_SECURE_BOOT_ENABLE: EfiGuid = + EfiGuid::new(0xf0a30bc7, 0xaf08, 0x4556, [0x99, 0xc4, 0x00, 0x10, 0x09, 0xc9, 0x3a, 0x44]); + /// GUID for the CustomMode variable. + /// + /// [Taken from TianoCore](https://github.com/tianocore/edk2/blob/e18d1c37e812284c5db1f2775db15ca349730138/SecurityPkg/SecurityPkg.dec#L96) + const EFI_CUSTOM_MODE_ENABLE: EfiGuid = + EfiGuid::new(0xc076ec0c, 0x7028, 0x4399, [0xa0, 0x72, 0x71, 0xee, 0x5c, 0x44, 0x8b, 0x9f]); + + /// GUID for the db variable. + /// + /// See UEFI Specification 32.6.1 UEFI Image Variable GUID & Variable Name. + const EFI_IMAGE_SECURITY_DATABASE: EfiGuid = + EfiGuid::new(0xd719b2cb, 0x3d3a, 0x4596, [0xa3, 0xbc, 0xda, 0xd0, 0xe, 0x67, 0x65, 0x6f]); + /// GUID for the standard UEFI variable. + /// + /// See UEFI Specification 3.3 Globally Defined Variables. + const EFI_GLOBAL_VARIABLE: EfiGuid = + EfiGuid::new(0x8be4df61, 0x93ca, 0x11d2, [0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c]); + /// ID of an NVDATA FV filesystem. + /// + /// [Taken from TianoCore](https://github.com/tianocore/edk2/blob/e18d1c37e812284c5db1f2775db15ca349730138/OvmfPkg/VarStore.fdf.inc#L23) + const EFI_SYSTEM_NV_DATA_FV: EfiGuid = + EfiGuid::new(0xfff12b8d, 0x7696, 0x4c8b, [0xa9, 0x85, 0x27, 0x47, 0x07, 0x5b, 0x4f, 0x50]); + /// ID of an authenticated variable. + /// + /// [Taken from TianoCore](https://github.com/tianocore/edk2/blob/e18d1c37e812284c5db1f2775db15ca349730138/OvmfPkg/EmuVariableFvbRuntimeDxe/Fvb.c#L31) + const EFI_AUTHENTICATED_VARIABLE: EfiGuid = + EfiGuid::new(0xaaf32c78, 0x947b, 0x439a, [0xa1, 0x80, 0x2e, 0x14, 0x4e, 0xc3, 0x77, 0x92]); + /// ID of an EFI Encryption Key in x509 certificate mode. + /// + /// See UEFI Specification 32.4.1 Signature Database. + #[allow(dead_code)] + const EFI_CERT_X509: EfiGuid = + EfiGuid::new(0xa5c059a1, 0x94e4, 0x4aa7, [0x87, 0xb5, 0xab, 0x15, 0x5c, 0x2b, 0xf0, 0x72]); +} + +/// Timestamps as encoded in UEFI. +/// +/// See UEFI SPecification 8.3 Time Services. +#[derive(Serialize)] +struct EfiTime { + /// The current local year. Valid range is 1900-9999. + year: u16, + /// The current local month. Valid range is 1-12. + month: u8, + /// The current local day. Valid range is 1-31. + day: u8, + /// The current local hour. Valid range is 0-23. + hour: u8, + /// The current local minute. Valid range is 0-59. + minute: u8, + /// The current local second. Valid range is 0-59. + second: u8, + #[doc(hidden)] + pad1: u8, + /// The current local nanosecond, as a fraction of the current second. Valid + /// range is 0-999,999,999. + nanosecond: u32, + /// The time's offset in minutes from UTC. If the value is 0x7FF, then the + /// time is interpreted as a local time. + timezone: i16, + /// A bitmask containing the daylight savings time information for the time. + daylight: u8, + #[doc(hidden)] + pad2: u8 +} + +impl EfiTime { + /// Get the current time. + fn current() -> EfiTime { + let curtime = Utc::now(); + EfiTime { + // Panic on negative year. This shouldn't happen unless system clock + // is set before year 1... + year: u16::try_from(curtime.year()).unwrap(), + month: curtime.month() as u8, + day: curtime.day() as u8, + hour: curtime.hour() as u8, + minute: curtime.minute() as u8, + second: curtime.second() as u8, + pad1: 0, + nanosecond: curtime.nanosecond() as u32, + timezone: 0, + daylight: 0, + pad2: 0 + } + } +} + +/// Header of the NVRAM used by OVMF. +/// +/// [Taken from TianoCore](https://github.com/tianocore/edk2/blob/e18d1c37e812284c5db1f2775db15ca349730138/MdePkg/Include/Pi/PiFirmwareVolume.h#L99) +#[derive(Serialize)] +struct EfiFirmwareVolume { + /// A vector of zero bytes. + zero_vector: [u8; 16], + /// The GUID of the current FirmwareVolume. OVMF will likely change this ID + /// if they ever make breaking changes to the format to keep compatibility + /// with the old format. + /// + /// The current GUID used is EFI_SYSTEM_NV_DATA_FV. + filesystem_guid: EfiGuid, + /// Length in bytes of the complete firmware volume, including the header. + fv_length: u64, + /// Should be b"_FVH". + signature: u32, + /// Declares capabilities and power-on defaults for the firmware volume. + attributes: u32, + /// Length in bytes of the complete firmware volume header. Contains the + /// block map list. + header_length: u16, + /// A 16-bit checksum of the firmware volume header (from the zero_vector + /// and using the size stored in header_length). A valid header sums to + /// zero. + /// + /// [Checksum algorithm in TianoCore](https://github.com/tianocore/edk2/blob/e18d1c37e812284c5db1f2775db15ca349730138/MdePkg/Library/BaseLib/CheckSum.c#L130). + checksum: u16, + /// Offset, relative to the start of the header, of the extended header + /// (EFI_FIRMWARE_VOLUME_EXT_HEADER) or zero if there is no extended header. + ext_header_offset: u16, + /// This field must always be set to 0. + reserved: u8, + /// Set to 2. Future versions of this specification may define new header fields and will + /// increment the Revision field accordingly. + revision: u8, + // block_map goes until it finds a {0, 0} +} + +/// Taken from [TianoCore](https://github.com/tianocore/edk2/blob/e18d1c37e812284c5db1f2775db15ca349730138/MdePkg/Include/Pi/PiFirmwareVolume.h#L85). +#[derive(Serialize)] +struct EfiFvBlockMapEntry { + /// The number of sequential blocks which are of the same size. + num_blocks: u32, + /// The size of the blocks. + length: u32 +} + +/// Variable store region header. +/// +/// Taken from [TianoCore](https://github.com/tianocore/edk2/blob/e18d1c37e812284c5db1f2775db15ca349730138/MdeModulePkg/Include/Guid/VariableFormat.h#L67). +#[derive(Serialize)] +struct VariableStoreHeader { + /// Variable store region signature. Should be EFI_AUTHENTICATED_VARIABLE. + signature: EfiGuid, + /// Size of entire variable store, including size of variable store header + /// but not including the size of FvHeader. + size: u32, + /// Variable region format state. + format: u8, + /// Variable region healthy state. + state: u8, + #[doc(hidden)] + reserved: u16, + #[doc(hidden)] + reserved1: u32 +} + +bitflags! { + #[derive(Serialize)] + struct VariableAttributes: u32 { + /// Variable is stored in non-volatile storage and will persist across + /// power cycles. + const NON_VOLATILE = 0x00000001; + /// Behavior is not documented in UEFI spec... + const BOOTSERVICE_ACCESS = 0x00000002; + /// If EFI_BOOT_SERVICES.ExitBootServices() has already been executed, + /// data variables without the RUNTIME_ACCESS attribute set will not be + /// visible to GetVariable() and will return an EFI_NOT_FOUND error. + const RUNTIME_ACCESS = 0x00000004; + /// If HARDWARE_ERROR_RECORD attribute is set, VariableName and + /// VendorGuid must comply with the rules stated in Section 8.2.4.2 and + /// Appendix P of the UEFI Spec. Otherwise, the SetVariable() call shall + /// return EFI_INVALID_PARAMETER. + const HARDWARE_ERROR_RECORD = 0x00000008; + /// AUTHENTICATED_WRITE_ACCESS is deprecated and should be considered + /// reserved. + const AUTHENTICATED_WRITE_ACCESS = 0x00000010; + /// Secure Boot Policy Variable must be created with the + /// TIME_BASED_AUTHENTICATED_WRITE_ACCESS attribute set, and the + /// authentication shall use the EFI_VARIABLE_AUTHENTICATION_2 descriptor. + /// If the appropriate attribute bit is not set, then the firmware shall + /// return EFI_INVALID_PARAMETER. + const TIME_BASED_AUTHENTICATED_WRITE_ACCESS = 0x00000020; + /// Never returned in GetVariable. Used to signal that we want to append + /// instead of overwrite when writing a variable with SetVariable. + const APPEND_WRITE = 0x00000040; + /// This attribute indicates that the variable payload begins with an + /// AUTHENTICATION_3 structure, and potentially more structures as + /// indicated by fields of this structure. + const ENHANCED_AUTHENTICATED_ACCESS = 0x00000080; + } +} + +/// Single authenticated variable data header structure. +#[derive(Serialize)] +struct AuthenticatedVariableHeader { + /// Variable data start flag. Should be set to 0x55AA. + startid: u16, + /// Variable state. 0x3F means VAR_ADDED. + state: u8, + #[doc(hidden)] + reserved: u8, + /// Attributes of variable defined in UEFI specification. See + /// [VariableAttributes]. + attributes: VariableAttributes, + /// Associated monotonic count value against replay attack. + monotonic_count: u64, + /// Associated TimeStamp value against replay attack. + timestamp: EfiTime, + /// Index of associated public key in database. + pub_key_index: u32, + /// Size of variable null-terminated Unicode string name. + name_size: u32, + /// Size of the variable data without this header. + data_size: u32, + /// A unique identifier for the vendor that produces and consumes this + /// variable. + vendor_guid: EfiGuid +} + +/// Serialize a secure variable. +fn serialize_var(mut file: &mut File, name: &str, guid: EfiGuid, attributes: VariableAttributes, data: &[u8]) { + let curpos = file.seek(SeekFrom::Current(0)).unwrap(); + let aligned_pos = (curpos + 4 - 1) & !(4 - 1); + file.seek(SeekFrom::Start(aligned_pos)).unwrap(); + + // The OVMF code realigns if necessary here, but it does so with an + // alignment requirement of 1... ಠ_ಠ + bincode::serialize_into(&mut file, &AuthenticatedVariableHeader { + startid: 0x55AA, + state: 0x3F, + reserved: 0, + attributes: attributes, + monotonic_count: 0, + timestamp: EfiTime::current(), + pub_key_index: 0, + name_size: (name.len() as u32 + 1) * 2, + data_size: data.len() as u32, + vendor_guid: guid, + }).unwrap(); + let name = &name.encode_utf16().chain(std::iter::once(0)).map(|v| v.to_le_bytes()).collect::>(); + let name = name.iter().flatten().cloned().collect::>(); + file.write_all(&name).unwrap(); + file.write_all(data).unwrap(); +} + +fn main() { + /// Size of our firmware volume. 128KiB. + const FV_LENGTH: u64 = 0x20000; + /// Size of a block in our firmware volume. 4 KiB. + const FV_BLOCKSIZE: u32 = 0x1000; + let mut file = File::create("target/OVMF_VARS.fd").unwrap(); + bincode::serialize_into(&mut file, &EfiFirmwareVolume { + zero_vector: [0; 16], + filesystem_guid: EfiGuid::EFI_SYSTEM_NV_DATA_FV, + fv_length: FV_LENGTH, + signature: u32::from_le_bytes(*b"_FVH"), + attributes: 0x4FEFF, + header_length: u16::try_from(size_of::() + 2 * size_of::()).unwrap(), + checksum: 0xF919, + ext_header_offset: 0, + reserved: 0, + revision: 2, + }).unwrap(); + bincode::serialize_into(&mut file, &EfiFvBlockMapEntry { + num_blocks: u32::try_from(FV_LENGTH / u64::from(FV_BLOCKSIZE)).unwrap(), + length: FV_BLOCKSIZE + }).unwrap(); + bincode::serialize_into(&mut file, &EfiFvBlockMapEntry { + num_blocks: 0, + length:0 + }).unwrap(); + + let curpos = file.seek(SeekFrom::Current(0)).unwrap(); + let aligned_pos = (curpos + 4 - 1) & !(4 - 1); + file.seek(SeekFrom::Start(aligned_pos)).unwrap(); + + bincode::serialize_into(&mut file, &VariableStoreHeader { + signature: EfiGuid::EFI_AUTHENTICATED_VARIABLE, + size: 0xDFB8, + format: 0x5A, + state: 0xFE, + reserved: 0, + reserved1: 0, + }).unwrap(); + + let data = std::fs::read("target/keys/PK.esl").unwrap(); + let secure_var_attrs : VariableAttributes = VariableAttributes::NON_VOLATILE | + VariableAttributes::BOOTSERVICE_ACCESS | VariableAttributes::TIME_BASED_AUTHENTICATED_WRITE_ACCESS; + + serialize_var(&mut file, "db", EfiGuid::EFI_IMAGE_SECURITY_DATABASE, secure_var_attrs | VariableAttributes::RUNTIME_ACCESS, &data); + serialize_var(&mut file, "KEK", EfiGuid::EFI_GLOBAL_VARIABLE, secure_var_attrs | VariableAttributes::RUNTIME_ACCESS, &data); + serialize_var(&mut file, "PK", EfiGuid::EFI_GLOBAL_VARIABLE, secure_var_attrs | VariableAttributes::RUNTIME_ACCESS, &data); + serialize_var(&mut file, "VendorKeysNv", EfiGuid::EFI_VENDOR_KEYS_NV, secure_var_attrs, &[0]); + serialize_var(&mut file, "SecureBootEnable", EfiGuid::EFI_SECURE_BOOT_ENABLE, VariableAttributes::NON_VOLATILE | VariableAttributes::BOOTSERVICE_ACCESS, &[1]); + serialize_var(&mut file, "CustomMode", EfiGuid::EFI_CUSTOM_MODE_ENABLE, VariableAttributes::NON_VOLATILE | VariableAttributes::BOOTSERVICE_ACCESS, &[0]); + let mut curpos = file.seek(SeekFrom::Current(0)).unwrap(); + while curpos < FV_LENGTH { + let data = [0xFF; 0x1000]; + let maxsize = std::cmp::min(FV_LENGTH - curpos, data.len() as u64); + let written = file.write(&data[..maxsize as usize]).unwrap(); + curpos += written as u64; + } +} \ No newline at end of file diff --git a/ipcdefs/loader.id b/ipcdefs/loader.id index 9b33214fc..e274d0e35 100644 --- a/ipcdefs/loader.id +++ b/ipcdefs/loader.id @@ -2,9 +2,12 @@ # # Responsible for creating, loading, starting and waiting on processes. interface sunrise_libuser::ldr::ILoaderInterface is ldr:shel { - # Create, load and start the process `title_name` with the given args. - # Returns the process' pid. - [0] launch_title(array title_name, array args) -> u64 pid; + # Create and load the process `title_name` with the given args. + # Returns the process' pid. The process will not be started yet, use + # `launch_title` to start it. + [0] create_title(array title_name, array args) -> u64 pid; + # Starts a process created with create_title. + [2] launch_title(u64 pid); # Wait for the process with the given pid, returning the exit status. [1] wait(u64 pid) -> u32 exit_status; } \ No newline at end of file diff --git a/ipcdefs/twili.id b/ipcdefs/twili.id index 6b37cea25..be73a271f 100644 --- a/ipcdefs/twili.id +++ b/ipcdefs/twili.id @@ -11,13 +11,16 @@ interface sunrise_libuser::twili::ITwiliService is twili { object stdin, object stdout, object stderr); + + # Creates a pipe whose read side gets sent to the write side. + [1] create_pipe() -> object; } # The Twili Manager is responsible for registering a process' pipes. The PM # should connect to this service and register pipes before starting a process. interface sunrise_libuser::twili::ITwiliManagerService is twili:m { # Registers the pipe of a remote process. - [0] register_pipes(pid, + [0] register_pipes(u64 pid, object stdin, object stdout, object stderr); diff --git a/isofiles/boot/grub/grub.cfg b/isofiles/boot/grub/grub.cfg index 62f52b6e2..c34e6aab0 100644 --- a/isofiles/boot/grub/grub.cfg +++ b/isofiles/boot/grub/grub.cfg @@ -12,12 +12,12 @@ insmod gettext font=unicode terminal_input console -terminal_output gfxterm +terminal_output console gfxterm insmod png background_image -m stretch /boot/grub/splash_ioapic.png -menuentry "my os" { +menuentry "my os" --unrestricted { multiboot2 /boot/sunrise-bootstrap "info" module2 /boot/sunrise-kernel kernel module2 /boot/sunrise-shell shell diff --git a/keyboard/src/ps2.rs b/keyboard/src/ps2.rs index 756bff785..8b2762118 100644 --- a/keyboard/src/ps2.rs +++ b/keyboard/src/ps2.rs @@ -331,6 +331,18 @@ impl PS2 { Released => { self.is_right_shift.store(false, SeqCst); } } } + HidKeyboardScancode::LeftCtrl => { + match state { + Pressed => { self.is_left_ctrl.store(true, SeqCst); } + Released => { self.is_left_ctrl.store(false, SeqCst); } + } + } + HidKeyboardScancode::RightCtrl => { + match state { + Pressed => { self.is_right_ctrl.store(true, SeqCst); } + Released => { self.is_right_ctrl.store(false, SeqCst); } + } + } _ => { debug!("Keyboard: {} {:?}", match state { Pressed => "pressed ", Released => "released" }, key); } } } @@ -342,7 +354,7 @@ impl PS2 { true => char::from(key.upper_case) } } - + /// Get a bitfield representing the modifiers of this keyboard fn encode_modifiers(&self, state: State) -> u8 { let caps_locked = self.is_capslocked.load(SeqCst) as u8; diff --git a/libuser/src/ps2.rs b/libuser/src/ps2.rs index 1839759aa..d849fb425 100644 --- a/libuser/src/ps2.rs +++ b/libuser/src/ps2.rs @@ -64,6 +64,15 @@ impl Keyboard { } } + /// Asynchronously waits for a single keystate change, and return the raw + /// change. + pub fn read_keystate_async<'a>(&'a mut self, queue: WorkQueue<'_>) -> impl core::future::Future + Unpin + 'a { + let Keyboard { ref mut inner, ref mut readable_event } = self; + readable_event.wait_async_cb(queue, move || { + inner.try_read_keystate() + }) + } + /// Asynchronously waits for a single key press, and return its unicode /// representation. pub fn read_key_async<'a>(&'a mut self, queue: WorkQueue<'_>) -> impl core::future::Future + Unpin + 'a { @@ -78,6 +87,12 @@ impl Keyboard { pub fn try_read_key(&mut self) -> Option { self.inner.try_read_key() } + + /// If a keystate change is pending, return the raw keystate. This can be + /// used to implement poll-based or asynchronous reading from keyboard. + pub fn try_read_keystate(&mut self) -> Option { + self.inner.try_read_keystate() + } } impl InnerKeyboard { @@ -142,4 +157,18 @@ impl InnerKeyboard { res => res } } + + /// If a keystate change is pending, return the raw keystate. This can be + /// used to implement poll-based or asynchronous reading from keyboard. + pub fn try_read_keystate(&mut self) -> Option { + // Try to read a key from the cache + match self.keys_queue.pop_front() { + // In the case we don't find anything, we force an IPC update and retry to read in the cache. + None => { + self.update_keys(); + self.keys_queue.pop_front() + } + res => res + } + } } \ No newline at end of file diff --git a/libuser/src/syscalls.rs b/libuser/src/syscalls.rs index 1bd2f684a..70f5f8bc2 100644 --- a/libuser/src/syscalls.rs +++ b/libuser/src/syscalls.rs @@ -724,7 +724,7 @@ pub(crate) fn reset_signal(event: HandleRef) -> Result<(), KernelError> { /// - The given handle is invalid or not a process. pub fn get_process_id(process_handle: &Process) -> Result { unsafe { - let (pid, ..) = syscall(nr::GetProcessInfo, (process_handle.0).0.get() as usize, 0, 0, 0, 0, 0)?; + let (pid, ..) = syscall(nr::GetProcessId, (process_handle.0).0.get() as usize, 0, 0, 0, 0, 0)?; Ok(pid as _) } } \ No newline at end of file diff --git a/libuser/src/terminal.rs b/libuser/src/terminal.rs index d40bc24a6..20602d54d 100644 --- a/libuser/src/terminal.rs +++ b/libuser/src/terminal.rs @@ -96,6 +96,11 @@ impl Terminal { Ok(()) } + /// Clone this terminal's pipe. + pub fn clone_pipe(&self) -> Result { + self.pipe.clone_current_object() + } + /// Read a line of text. Note that it might return without reading an entire /// line if the buffer is not big enough. The user should check if a \n is /// present in data. diff --git a/libuser/src/types.rs b/libuser/src/types.rs index d883122aa..1ba4e6490 100644 --- a/libuser/src/types.rs +++ b/libuser/src/types.rs @@ -284,6 +284,20 @@ impl ClientSession { mem::forget(self); handle } + + /// Clones the current object, returning a new handle. The returned handle + /// has its own IPC buffer - it may be used concurrently with the original. + pub fn try_clone(&self) -> Result { + let mut buf = [0; 0x100]; + let mut msg = Message::<(), [_; 0], [_; 0], [_; 0]>::new_request(None, 2); + msg.set_ty(MessageTy::Control); + msg.pack(&mut buf[..]); + self.send_sync_request_with_user_buffer(&mut buf[..])?; + let mut res: Message<'_, (), [_; 0], [_; 0], [_; 1]> = Message::unpack(&buf[..]); + res.error()?; + let handle = res.pop_handle_move()?; + Ok(ClientSession(handle)) + } } impl Drop for ClientSession { diff --git a/loader/src/main.rs b/loader/src/main.rs index b08b0a683..ebf04ed78 100644 --- a/loader/src/main.rs +++ b/loader/src/main.rs @@ -65,7 +65,7 @@ lazy_static! { } /// Start the given titleid by loading its content from the provided filesystem. -fn boot(fs: &IFileSystemProxy, titlename: &str, args: &[u8]) -> Result { +fn boot(fs: &IFileSystemProxy, titlename: &str, args: &[u8], start: bool) -> Result { info!("Booting titleid {}", titlename); let val = format!("/bin/{}/main", titlename); @@ -180,10 +180,12 @@ fn boot(fs: &IFileSystemProxy, titlename: &str, args: &[u8]) -> Result, title_name: &[u8], args: &[u8]) -> FutureObj<'_, Result> { + fn create_title(&mut self, _workqueue: WorkQueue<'static>, title_name: &[u8], args: &[u8]) -> FutureObj<'_, Result> { let res = (|| -> Result { let title_name = str::from_utf8(title_name).or(Err(LoaderError::ProgramNotFound))?; - let Pid(pid) = boot(&*BOOT_FROM_FS, title_name, args)?; + let Pid(pid) = boot(&*BOOT_FROM_FS, title_name, args, false)?; Ok(pid) })(); FutureObj::new(Box::new(async move { @@ -216,6 +218,23 @@ impl ILoaderInterfaceAsync for LoaderIface { })) } + fn launch_title(&mut self, _workqueue: WorkQueue<'static>, pid: u64) -> FutureObj<'_, Result<(), Error>> { + let res = (|| -> Result<(), Error> { + let lock = PROCESSES.lock(); + let process = lock.get(&pid) + .ok_or(PmError::PidNotFound)?; + debug!("Starting process."); + if let Err(err) = process.start(0, 0, PAGE_SIZE as u32 * 32) { + error!("Failed to start pid {}: {}", pid, err); + return Err(err) + } + Ok(()) + })(); + FutureObj::new(Box::new(async move { + res + })) + } + fn wait(&mut self, workqueue: WorkQueue<'static>, pid: u64) -> FutureObj<'_, Result> { FutureObj::new(Box::new(async move { // Weird logic: we create an as_ref_static process, and then we'll @@ -292,7 +311,7 @@ fn main() { .find(|(_, v)| **v == b'/' || **v == b'\0') .map(|(idx, _)| idx).unwrap_or_else(|| entry.path.len()); if let Ok(titleid) = str::from_utf8(&entry.path[5..endpos]) { - let _ = boot(&fs, titleid, &[]); + let _ = boot(&fs, titleid, &[], true); } else { error!("Non-ASCII titleid found in /boot."); continue; diff --git a/rust/src/libstd/Cargo.toml b/rust/src/libstd/Cargo.toml index 862792b76..cce253ec8 100644 --- a/rust/src/libstd/Cargo.toml +++ b/rust/src/libstd/Cargo.toml @@ -60,6 +60,8 @@ fortanix-sgx-abi = { version = "0.3.2", features = ['rustc-dep-of-std'] } # Need #SunriseOS/SunriseOS#402 to actually build sunrise-libuser = { git = "https://github.com/sunriseos/sunriseos.git", default-features = false, features = ['rustc-dep-of-std'] } lazy_static = { version = "1.3", features = ['spin_no_std'] } +log = "0.4" +spin = "0.5" [build-dependencies] cc = "1.0" diff --git a/rust/src/libstd/sys/sunrise/mod.rs b/rust/src/libstd/sys/sunrise/mod.rs index d30c317fe..647c32e2a 100644 --- a/rust/src/libstd/sys/sunrise/mod.rs +++ b/rust/src/libstd/sys/sunrise/mod.rs @@ -37,6 +37,11 @@ pub mod thread_local; #[cfg(not(test))] pub fn init() { + use core::intrinsics::abort; + if let Err(err) = stdio::init() { + log::error!("Error initializing stdio! {:?}", err); + unsafe { abort(); } + } fs::init(); } diff --git a/rust/src/libstd/sys/sunrise/process.rs b/rust/src/libstd/sys/sunrise/process.rs index 27829dbd6..9e78d8444 100644 --- a/rust/src/libstd/sys/sunrise/process.rs +++ b/rust/src/libstd/sys/sunrise/process.rs @@ -102,7 +102,8 @@ impl Command { }; // TODO(Sunrise): Remap error codes - let pid = interface.launch_title(self.program.as_bytes(), command_line.as_bytes()).unwrap(); + let pid = interface.create_title(self.program.as_bytes(), command_line.as_bytes()).unwrap(); + interface.launch_title(pid).unwrap(); let child = Process { pid, diff --git a/rust/src/libstd/sys/sunrise/stdio.rs b/rust/src/libstd/sys/sunrise/stdio.rs index 9445561a8..51c50317d 100644 --- a/rust/src/libstd/sys/sunrise/stdio.rs +++ b/rust/src/libstd/sys/sunrise/stdio.rs @@ -1,9 +1,22 @@ use crate::io; +use sunrise_libuser::error::Error; +use sunrise_libuser::twili::{ITwiliServiceProxy, IPipeProxy}; +use spin::Once; + pub struct Stdin; pub struct Stdout; pub struct Stderr; +pub static PIPES: Once<(IPipeProxy, IPipeProxy, IPipeProxy)> = + Once::new(); + +pub fn init() -> Result<(), Error> { + let pipes = ITwiliServiceProxy::new()?.open_pipes()?; + PIPES.call_once(|| pipes); + Ok(()) +} + impl Stdin { pub fn new() -> io::Result { Ok(Stdin) @@ -11,8 +24,10 @@ impl Stdin { } impl io::Read for Stdin { - fn read(&mut self, _buf: &mut [u8]) -> io::Result { - panic!("not supported on sunrise yet") + fn read(&mut self, buf: &mut [u8]) -> io::Result { + PIPES.r#try() + .ok_or(io::Error::from(io::ErrorKind::NotFound)) + .and_then(|v| Ok(v.0.read(buf)? as usize)) } } @@ -24,14 +39,9 @@ impl Stdout { impl io::Write for Stdout { fn write(&mut self, buf: &[u8]) -> io::Result { - // TODO(Sunrise): Bufferize - use sunrise_libuser::syscalls::output_debug_string; - - let buf = unsafe { core::str::from_utf8_unchecked(buf) }; - - let _ = output_debug_string(buf, 50, "stdout"); - - Ok(buf.len()) + PIPES.r#try() + .ok_or(io::Error::from(io::ErrorKind::NotFound)) + .and_then(|v| { v.1.write(buf)?; Ok(buf.len()) }) } fn flush(&mut self) -> io::Result<()> { @@ -47,7 +57,6 @@ impl Stderr { impl io::Write for Stderr { fn write(&mut self, buf: &[u8]) -> io::Result { - // TODO(Sunrise): Bufferize use sunrise_libuser::syscalls::output_debug_string; let buf = unsafe { core::str::from_utf8_unchecked(buf) }; diff --git a/shell/src/main.rs b/shell/src/main.rs index f75a2d2e9..b19bb8c79 100644 --- a/shell/src/main.rs +++ b/shell/src/main.rs @@ -34,6 +34,7 @@ use crate::libuser::threads::{self, Thread}; use crate::libuser::error::{Error, LoaderError, FileSystemError}; use crate::libuser::syscalls; use crate::libuser::ps2::Keyboard; +use crate::libuser::twili::ITwiliManagerServiceProxy; use core::fmt::Write; use alloc::string::String; @@ -167,6 +168,7 @@ pub fn get_next_line(logger: &mut Terminal) -> String { fn main() { let mut terminal = Terminal::new(WindowSize::FontLines(-1, false)).unwrap(); let mut keyboard = Keyboard::new().unwrap(); + let twili = ITwiliManagerServiceProxy::new().unwrap(); let loader = ILoaderInterfaceProxy::raw_new().unwrap(); let fs_proxy = IFileSystemServiceProxy::raw_new().unwrap(); @@ -269,8 +271,15 @@ fn main() { }, name => { // Try to run it as an external binary. - let res = loader.launch_title(name.as_bytes(), line.as_bytes()) - .and_then(|pid| loader.wait(pid)); + let res = (|| { + let pid = loader.create_title(name.as_bytes(), line.as_bytes())?; + let stdin = terminal.clone_pipe()?; + let stdout = terminal.clone_pipe()?; + let stderr = terminal.clone_pipe()?; + twili.register_pipes(pid, stdin, stdout, stderr)?; + loader.launch_title(pid)?; + loader.wait(pid) + })(); match res { Err(Error::Loader(LoaderError::ProgramNotFound, _)) => { diff --git a/swipc-gen/src/gen_rust_code.rs b/swipc-gen/src/gen_rust_code.rs index 8c76644bd..f20015e57 100644 --- a/swipc-gen/src/gen_rust_code.rs +++ b/swipc-gen/src/gen_rust_code.rs @@ -890,6 +890,12 @@ pub fn generate_proxy(ifacename: &str, interface: &Interface) -> String { } writeln!(s, "impl {} {{", struct_name).unwrap(); + writeln!(s, " /// Clones the current object, returning a new handle.").unwrap(); + writeln!(s, " /// The returned handle has its own IPC buffer - it may be used concurrently with the original.").unwrap(); + writeln!(s, " pub fn clone_current_object(&self) -> Result {{").unwrap(); + writeln!(s, " Ok({}::from(self.0.try_clone()?))", struct_name).unwrap(); + writeln!(s, " }}").unwrap(); + for cmd in &interface.funcs { match format_cmd(&cmd) { Ok(out) => write!(s, "{}", out).unwrap(), diff --git a/twili/Cargo.toml b/twili/Cargo.toml new file mode 100644 index 000000000..2615bcc40 --- /dev/null +++ b/twili/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "sunrise-twili" +version = "0.1.0" +authors = ["roblabla "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +sunrise-libuser = { path = "../libuser" } +spin = "0.5" +log = "0.4" +lazy_static = { version = "1.3.0", features = ["spin_no_std"] } +core = { package = "core-futures-tls", version = "0.1" } \ No newline at end of file diff --git a/twili/src/main.rs b/twili/src/main.rs new file mode 100644 index 000000000..cd83d6f3a --- /dev/null +++ b/twili/src/main.rs @@ -0,0 +1,135 @@ +#![feature(async_await)] +#![no_std] + +extern crate alloc; + +use core::cmp::min; +use alloc::boxed::Box; +use alloc::collections::BTreeMap; +use alloc::collections::VecDeque; +use alloc::sync::Arc; + +use spin::Mutex; +use lazy_static::lazy_static; + +use sunrise_libuser::{kip_header, capabilities}; +use sunrise_libuser::error::{Error, PmError}; +use sunrise_libuser::ipc; +use sunrise_libuser::ipc::server::{port_handler, new_session_wrapper}; +use sunrise_libuser::futures::{WaitableManager, WorkQueue}; +use sunrise_libuser::futures_rs::future::FutureObj; +use sunrise_libuser::syscalls; +use sunrise_libuser::twili::{ITwiliManagerService, ITwiliService, IPipeProxy, IPipeAsync}; +use sunrise_libuser::types::{WritableEvent, ReadableEvent, Pid}; + +#[derive(Debug, Default, Clone)] +struct TwiliManIface; +impl ITwiliManagerService for TwiliManIface { + fn register_pipes( + &mut self, + _manager: WorkQueue<'static>, + pid: u64, + stdin: IPipeProxy, + stdout: IPipeProxy, + stderr: IPipeProxy) -> Result<(), Error> + { + log::info!("Registering pipes for {}", pid); + PIPES.lock().insert(pid, (stdin, stdout, stderr)); + Ok(()) + } +} + +#[derive(Debug, Default, Clone)] +struct TwiliIface; +impl ITwiliService for TwiliIface { + fn open_pipes(&mut self, _manager: WorkQueue<'static>, pid: Pid) -> Result<(IPipeProxy, IPipeProxy, IPipeProxy), Error> { + log::info!("Opening pipes for {}", pid.0); + PIPES.lock().remove(&pid.0) + .ok_or(PmError::PidNotFound.into()) + } + + fn create_pipe(&mut self, manager: WorkQueue<'static>) -> Result { + let pipe = DumbPipe(Arc::new(Mutex::new(VecDeque::new()))); + let (server, client) = syscalls::create_session(false, 0)?; + let wrapper = new_session_wrapper(manager.clone(), server, pipe, DumbPipe::dispatch); + manager.spawn(FutureObj::new(Box::new(wrapper))); + Ok(IPipeProxy::from(client)) + } +} + +lazy_static! { + static ref PIPES: Mutex> = + Mutex::new(BTreeMap::new()); + static ref DATA_EVENT: (WritableEvent, ReadableEvent) = { + sunrise_libuser::syscalls::create_event().unwrap() + }; +} + +#[derive(Debug, Clone)] +struct DumbPipe(Arc>>); +impl IPipeAsync for DumbPipe { + fn read<'a>(&'a mut self, work_queue: WorkQueue<'static>, buf: &'a mut [u8]) -> FutureObj<'a, Result> { + FutureObj::new(Box::new(async move { + DATA_EVENT.1.wait_async_cb(work_queue.clone(), || { + self.0.lock().get(0).map(|_| ()) + }).await; + let mut locked = self.0.lock(); + let count = min(buf.len(), locked.len()); + for (idx, item) in locked.drain(..count).enumerate() { + buf[idx] = item; + } + Ok(count as u64) + })) + } + + fn write<'a>(&'a mut self, _manager: WorkQueue<'static>, buf: &'a [u8]) -> FutureObj<'a, Result<(), Error>> { + FutureObj::new(Box::new(async move { + self.0.lock().extend(buf); + DATA_EVENT.0.signal().unwrap(); + Ok(()) + })) + } +} + +fn main() { + let mut man = WaitableManager::new(); + + let handler = port_handler(man.work_queue(), "twili", TwiliIface::dispatch).unwrap(); + man.work_queue().spawn(FutureObj::new(Box::new(handler))); + let handler = port_handler(man.work_queue(), "twili:m", TwiliManIface::dispatch).unwrap(); + man.work_queue().spawn(FutureObj::new(Box::new(handler))); + + man.run(); +} + +kip_header!(HEADER = sunrise_libuser::caps::KipHeader { + magic: *b"KIP1", + name: *b"twili\0\0\0\0\0\0\0", + title_id: 0x0200000000006480, + process_category: sunrise_libuser::caps::ProcessCategory::KernelBuiltin, + main_thread_priority: 0, + default_cpu_core: 0, + flags: 0, + reserved: 0, + stack_page_count: 16, +}); + +capabilities!(CAPABILITIES = Capabilities { + svcs: [ + sunrise_libuser::syscalls::nr::SleepThread, + sunrise_libuser::syscalls::nr::ExitProcess, + sunrise_libuser::syscalls::nr::CloseHandle, + sunrise_libuser::syscalls::nr::WaitSynchronization, + sunrise_libuser::syscalls::nr::OutputDebugString, + sunrise_libuser::syscalls::nr::SetThreadArea, + + sunrise_libuser::syscalls::nr::SetHeapSize, + sunrise_libuser::syscalls::nr::QueryMemory, + sunrise_libuser::syscalls::nr::ConnectToNamedPort, + sunrise_libuser::syscalls::nr::SendSyncRequestWithUserBuffer, + + sunrise_libuser::syscalls::nr::ReplyAndReceiveWithUserBuffer, + sunrise_libuser::syscalls::nr::AcceptSession, + ], + raw_caps: [sunrise_libuser::caps::ioport(0x60), sunrise_libuser::caps::ioport(0x64), sunrise_libuser::caps::irq_pair(1, 0x3FF)] +}); \ No newline at end of file diff --git a/vi/Cargo.toml b/vi/Cargo.toml index 1614a398b..5c0644006 100644 --- a/vi/Cargo.toml +++ b/vi/Cargo.toml @@ -13,6 +13,7 @@ spin = "0.5" futures-preview = { version = "0.3.0-alpha.16", default-features = false, features = ["nightly", "alloc"] } font-rs = { git = "https://github.com/SunriseOS/font-rs", default-features = false } log = "0.4" +bit_field = "0.10" core = { package = "core-futures-tls", version = "0.1" } [dependencies.hashbrown] diff --git a/vi/src/terminal.rs b/vi/src/terminal.rs index eac52b7d0..1a4ea8db2 100644 --- a/vi/src/terminal.rs +++ b/vi/src/terminal.rs @@ -9,6 +9,7 @@ use font_rs::{font, font::{Font, GlyphBitmap}}; use hashbrown::HashMap; use spin::Mutex; use sunrise_libuser::error::{ViError, Error}; +use sunrise_libuser::keyboard::HidKeyboardStateType; use sunrise_libuser::futures::WorkQueue; use sunrise_libuser::mem::{find_free_address, PAGE_SIZE}; use sunrise_libuser::types::SharedMemory; @@ -20,6 +21,7 @@ use core::fmt::Write; use core::sync::atomic::Ordering; use sunrise_libuser::ps2::Keyboard; use crate::libuser::futures_rs::future::FutureObj; +use bit_field::BitField; /// Just an x and a y #[derive(Copy, Clone, Debug)] @@ -321,7 +323,35 @@ impl sunrise_libuser::twili::IPipeAsync for TerminalPipe { let mut keyboard = Keyboard::new().unwrap(); let mut i = 0; while buf.len() - i >= 4 { - let key = keyboard.read_key_async(manager.clone()).await; + let state = keyboard.read_keystate_async(manager.clone()).await; + + let key = if let HidKeyboardStateType::Ascii = state.state_type { + let lower_case = char::from(state.data); + let upper_case = char::from(state.additional_data); + let is_upper = state.modifiers.get_bit(0) || state.modifiers.get_bit(1) || state.modifiers.get_bit(2); + let is_pressed = state.modifiers.get_bit(7); + + if is_pressed { + if is_upper { + upper_case + } else { + lower_case + } + } else { + // We aren't pressed, so skip this entry. + continue; + } + } else { + continue; + }; + + let is_ctrl = state.modifiers.get_bit(3) || state.modifiers.get_bit(4); + + log::info!("{:?}", state); + if is_ctrl && key == 'd' { + // Ctrl-d pressed, early return. + return Ok(i as u64); + } if key == '\x08' && i == 0 { // Don't delete further than the first character.