diff --git a/.gitignore b/.gitignore index 162bd2b67bdf6a..0df79f9636ff00 100644 --- a/.gitignore +++ b/.gitignore @@ -152,3 +152,6 @@ x509.genkey # Clang's compilation database file /compile_commands.json + +# Rust (cargo) compilation artifacts +/target/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000000000..414ef254f79e66 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,374 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "aho-corasick" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "bindgen" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66c0bb6167449588ff70803f4127f0684f9063097eca5016f37eb52b92c2cf36" +dependencies = [ + "bitflags", + "cexpr", + "cfg-if", + "clang-sys", + "clap", + "env_logger", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "which", +] + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "cc" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66120af515773fb005778dc07c261bd201ec8ce50bd6e7144c927753fe013381" + +[[package]] +name = "cexpr" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "clang-sys" +version = "0.29.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6837df1d5cba2397b835c8530f51723267e16abbf83892e9e5af4f0e5dd10a" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "2.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "hermit-abi" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9" +dependencies = [ + "libc", +] + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + +[[package]] +name = "kernel" +version = "0.1.0" +dependencies = [ + "bindgen", + "bitflags", + "shlex 0.1.1", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "755456fae044e6fa1ebbbd1b3e902ae19e73097ed4ed87bb79934a867c007bc3" + +[[package]] +name = "libloading" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" +dependencies = [ + "cc", + "winapi", +] + +[[package]] +name = "log" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" + +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "memchr", + "version_check", +] + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "proc-macro2" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-syntax" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" + +[[package]] +name = "rust_example" +version = "0.1.0" +dependencies = [ + "kernel", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "shlex" +version = "0.1.1" + +[[package]] +name = "shlex" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "termcolor" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" + +[[package]] +name = "which" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724" +dependencies = [ + "libc", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000000000..26c9c7516ea0d4 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 + +# TODO: generate automatically +[workspace] +members = [ + "rust/shlex", + "rust/kernel", + "drivers/char/rust_example", +] + diff --git a/Documentation/index.rst b/Documentation/index.rst index 57719744774c2f..3a9ab325b9c53e 100644 --- a/Documentation/index.rst +++ b/Documentation/index.rst @@ -82,6 +82,7 @@ merged much easier. maintainer/index fault-injection/index livepatch/index + rust/index Kernel API documentation diff --git a/Documentation/rust/index.rst b/Documentation/rust/index.rst new file mode 100644 index 00000000000000..bcb4de39f3f17b --- /dev/null +++ b/Documentation/rust/index.rst @@ -0,0 +1,16 @@ +Rust +==== + +Documentation related to Rust within the kernel. If you are starting out, read the :ref:`Documentation/rust/quick-start.rst ` guide. + +.. toctree:: + :maxdepth: 1 + + quick-start + +.. only:: subproject and html + + Indices + ======= + + * :ref:`genindex` diff --git a/Documentation/rust/quick-start.rst b/Documentation/rust/quick-start.rst new file mode 100644 index 00000000000000..925848b82c2fa3 --- /dev/null +++ b/Documentation/rust/quick-start.rst @@ -0,0 +1,47 @@ +.. _rust_quick_start: + +Quick Start +=========== + +This document describes how to get started with Rust kernel development. + +Requirements +------------ + +A recent nightly Rust toolchain (at least ``rustc``, ``cargo`` and ``rustfmt``) is required, e.g. `nightly-2020-08-27`. In the future, this restriction will be lifted. If you are using ``rustup``, run:: + + rustup toolchain install nightly + +Otherwise, fetch a standalone installer from: + + https://www.rust-lang.org + +The sources for the compiler are required to be available. If you are using ``rustup``, run:: + + rustup component add rust-src + +Otherwise, if you used a standalone installer, you can clone the Rust compiler sources and create a symlink to them in the installation folder of your nightly toolchain:: + + git clone https://github.com/rust-lang/rust + ln -s rust .../rust-nightly/lib/rustlib/src + +Testing a simple driver +----------------------- + +If the kernel configuration system is able to find ``rustc`` and ``cargo``, it will enable Rust support (``CONFIG_HAS_RUST``). In turn, this will make visible the rest of options that depend on Rust. + +A simple driver you can compile to test things out is at ``drivers/char/rust_example`` (``CONFIG_RUST_EXAMPLE``). Enable it and compile the kernel with: + + make LLVM=1 + +TODO: drop LLVM=1, allowing to mix GCC with LLVM + +Avoiding the network +-------------------- + +You can prefetch the dependencies that ``cargo`` will download by running:: + + cargo fetch + +in the kernel sources root. After this step, a network connection is not needed anymore. + diff --git a/MAINTAINERS b/MAINTAINERS index f0068bceeb6158..44564ec6f2a3a9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15024,6 +15024,15 @@ L: linux-rdma@vger.kernel.org S: Maintained F: drivers/infiniband/ulp/rtrs/ +RUST +W: https://github.com/Rust-for-Linux +S: Maintained +K: \b(?i:rust)\b +L: linux-rust@vger.kernel.org (TODO: waiting for reply to my request) +F: rust/ +F: Documentation/rust/ +F: drivers/char/rust_example/ + RXRPC SOCKETS (AF_RXRPC) M: David Howells L: linux-afs@lists.infradead.org diff --git a/Makefile b/Makefile index f2116815416091..8307664d39015b 100644 --- a/Makefile +++ b/Makefile @@ -86,9 +86,11 @@ endif ifeq ($(KBUILD_VERBOSE),1) quiet = Q = + CARGO_VERBOSE = --verbose else quiet=quiet_ Q = @ + CARGO_VERBOSE = # TODO: could be --quiet if needed endif # If the user is running make -s (silent mode), suppress echoing of @@ -446,6 +448,8 @@ READELF = $(CROSS_COMPILE)readelf OBJSIZE = $(CROSS_COMPILE)size STRIP = $(CROSS_COMPILE)strip endif +RUSTC = rustc +CARGO = cargo PAHOLE = pahole RESOLVE_BTFIDS = $(objtree)/tools/bpf/resolve_btfids/resolve_btfids LEX = flex @@ -470,9 +474,13 @@ CHECKFLAGS := -D__linux__ -Dlinux -D__STDC__ -Dunix -D__unix__ \ -Wbitwise -Wno-return-void -Wno-unknown-attribute $(CF) NOSTDINC_FLAGS := CFLAGS_MODULE = +RUSTCFLAGS_MODULE = +CARGOFLAGS_MODULE = AFLAGS_MODULE = LDFLAGS_MODULE = CFLAGS_KERNEL = +RUSTCFLAGS_KERNEL = +CARGOFLAGS_KERNEL = AFLAGS_KERNEL = LDFLAGS_vmlinux = @@ -500,16 +508,27 @@ KBUILD_CFLAGS := -Wall -Wundef -Werror=strict-prototypes -Wno-trigraphs \ -Wno-format-security \ -std=gnu89 KBUILD_CPPFLAGS := -D__KERNEL__ +KBUILD_RUSTCFLAGS := +# TODO: a simple way to update `Cargo.lock` when we add a new driver +# TODO: another option is using explicit target specs, e.g. +# `--target=$(srctree)/arch/$(SRCARCH)/rust-target-spec.json` +KBUILD_CARGOFLAGS := $(CARGO_VERBOSE) --locked \ + -Z build-std=core,alloc -Z unstable-options \ + --out-dir=out --target=x86_64-linux-kernel KBUILD_AFLAGS_KERNEL := KBUILD_CFLAGS_KERNEL := +KBUILD_RUSTCFLAGS_KERNEL := +KBUILD_CARGOFLAGS_KERNEL := KBUILD_AFLAGS_MODULE := -DMODULE KBUILD_CFLAGS_MODULE := -DMODULE +KBUILD_RUSTCFLAGS_MODULE := --cfg MODULE +KBUILD_CARGOFLAGS_MODULE := KBUILD_LDFLAGS_MODULE := export KBUILD_LDS_MODULE := $(srctree)/scripts/module-common.lds KBUILD_LDFLAGS := CLANG_FLAGS := -export ARCH SRCARCH CONFIG_SHELL BASH HOSTCC KBUILD_HOSTCFLAGS CROSS_COMPILE LD CC +export ARCH SRCARCH CONFIG_SHELL BASH HOSTCC KBUILD_HOSTCFLAGS CROSS_COMPILE LD CC RUSTC CARGO export CPP AR NM STRIP OBJCOPY OBJDUMP OBJSIZE READELF PAHOLE RESOLVE_BTFIDS LEX YACC AWK INSTALLKERNEL export PERL PYTHON PYTHON3 CHECK CHECKFLAGS MAKE UTS_MACHINE HOSTCXX export KGZIP KBZIP2 KLZOP LZMA LZ4 XZ ZSTD @@ -518,9 +537,11 @@ export KBUILD_HOSTCXXFLAGS KBUILD_HOSTLDFLAGS KBUILD_HOSTLDLIBS LDFLAGS_MODULE export KBUILD_CPPFLAGS NOSTDINC_FLAGS LINUXINCLUDE OBJCOPYFLAGS KBUILD_LDFLAGS export KBUILD_CFLAGS CFLAGS_KERNEL CFLAGS_MODULE export CFLAGS_KASAN CFLAGS_KASAN_NOSANITIZE CFLAGS_UBSAN CFLAGS_KCSAN +export KBUILD_RUSTCFLAGS RUSTCFLAGS_KERNEL RUSTCFLAGS_MODULE +export KBUILD_CARGOFLAGS CARGOFLAGS_KERNEL CARGOFLAGS_MODULE export KBUILD_AFLAGS AFLAGS_KERNEL AFLAGS_MODULE -export KBUILD_AFLAGS_MODULE KBUILD_CFLAGS_MODULE KBUILD_LDFLAGS_MODULE -export KBUILD_AFLAGS_KERNEL KBUILD_CFLAGS_KERNEL +export KBUILD_AFLAGS_MODULE KBUILD_CFLAGS_MODULE KBUILD_RUSTCFLAGS_MODULE KBUILD_CARGOFLAGS_MODULE KBUILD_LDFLAGS_MODULE +export KBUILD_AFLAGS_KERNEL KBUILD_CFLAGS_KERNEL KBUILD_RUSTCFLAGS_KERNEL KBUILD_CARGOFLAGS_KERNEL # Files to ignore in find ... statements @@ -733,12 +754,21 @@ KBUILD_CFLAGS += $(call cc-disable-warning, format-truncation) KBUILD_CFLAGS += $(call cc-disable-warning, format-overflow) KBUILD_CFLAGS += $(call cc-disable-warning, address-of-packed-member) +# TODO: perhaps we could have some independent options for Rust only +KBUILD_CARGOFLAGS += --release + ifdef CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE KBUILD_CFLAGS += -O2 +KBUILD_RUSTCFLAGS += # TODO: do we want -C opt-level=2 here? +KBUILD_CARGOFLAGS += else ifdef CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE_O3 KBUILD_CFLAGS += -O3 +KBUILD_RUSTCFLAGS += # flags already passed by cargo's --release +KBUILD_CARGOFLAGS += else ifdef CONFIG_CC_OPTIMIZE_FOR_SIZE KBUILD_CFLAGS += -Os +KBUILD_RUSTCFLAGS += -C opt-level=z +KBUILD_CARGOFLAGS += endif # Tell gcc to never replace conditional load with a non-conditional one @@ -814,6 +844,10 @@ KBUILD_CFLAGS += -enable-trivial-auto-var-init-zero-knowing-it-will-be-removed-f endif DEBUG_CFLAGS := $(call cc-option, -fno-var-tracking-assignments) +DEBUG_RUSTCFLAGS := +DEBUG_CARGOFLAGS := + +# TODO: see what we should set for rustc/cargo for debug ifdef CONFIG_DEBUG_INFO ifdef CONFIG_DEBUG_INFO_SPLIT @@ -841,6 +875,12 @@ endif KBUILD_CFLAGS += $(DEBUG_CFLAGS) export DEBUG_CFLAGS +KBUILD_RUSTCFLAGS += $(DEBUG_RUSTCFLAGS) +export DEBUG_RUSTCFLAGS + +KBUILD_CARGOFLAGS += $(DEBUG_CARGOFLAGS) +export DEBUG_CARGOFLAGS + ifdef CONFIG_FUNCTION_TRACER ifdef CONFIG_FTRACE_MCOUNT_RECORD # gcc 5 supports generating the mcount tables directly @@ -972,10 +1012,12 @@ include $(addprefix $(srctree)/, $(include-y)) # Do not add $(call cc-option,...) below this line. When you build the kernel # from the clean source tree, the GCC plugins do not exist at this point. -# Add user supplied CPPFLAGS, AFLAGS and CFLAGS as the last assignments +# Add user supplied CPPFLAGS, AFLAGS, CFLAGS, RUSTCFLAGS and CARGOFLAGS as the last assignments KBUILD_CPPFLAGS += $(KCPPFLAGS) KBUILD_AFLAGS += $(KAFLAGS) KBUILD_CFLAGS += $(KCFLAGS) +KBUILD_RUSTCFLAGS += $(KRUSTCFLAGS) +KBUILD_CARGOFLAGS += $(KCARGOFLAGS) KBUILD_LDFLAGS_MODULE += --build-id LDFLAGS_vmlinux += --build-id @@ -1100,6 +1142,10 @@ export MODULES_NSDEPS := $(extmod-prefix)modules.nsdeps ifeq ($(KBUILD_EXTMOD),) core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/ +ifdef CONFIG_HAS_RUST +core-y += rust/ +endif + vmlinux-dirs := $(patsubst %/,%,$(filter %/, \ $(core-y) $(core-m) $(drivers-y) $(drivers-m) \ $(libs-y) $(libs-m))) @@ -1483,13 +1529,17 @@ DISTCLEAN_FILES += tags TAGS cscope* GPATH GTAGS GRTAGS GSYMS # clean: rm-files := $(CLEAN_FILES) -PHONY += archclean vmlinuxclean +PHONY += archclean vmlinuxclean cargoclean vmlinuxclean: $(Q)$(CONFIG_SHELL) $(srctree)/scripts/link-vmlinux.sh clean $(Q)$(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) clean) -clean: archclean vmlinuxclean +# TODO: clean particular subfolders via `Makefile.clean` +cargoclean: + $(Q)$(CARGO) clean + +clean: archclean vmlinuxclean cargoclean # mrproper - Delete all generated files, including .config # diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index b1bd336761b164..e12a7608e43aef 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -470,6 +470,18 @@ config ADI and SSM (Silicon Secured Memory). Intended consumers of this driver include crash and makedumpfile. +config RUST_EXAMPLE + depends on HAS_RUST + tristate "Rust Example" + default n + help + This option builds an example module written in Rust. + + To compile this as a module, choose M here: + the module will be called rust_example. + + If unsure, say N. + endmenu config RANDOM_TRUST_CPU diff --git a/drivers/char/Makefile b/drivers/char/Makefile index ffce287ef41551..b254119cb97df0 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -47,3 +47,5 @@ obj-$(CONFIG_PS3_FLASH) += ps3flash.o obj-$(CONFIG_XILLYBUS) += xillybus/ obj-$(CONFIG_POWERNV_OP_PANEL) += powernv-op-panel.o obj-$(CONFIG_ADI) += adi.o + +obj-$(CONFIG_RUST_EXAMPLE) += rust_example/ diff --git a/drivers/char/rust_example/Cargo.toml b/drivers/char/rust_example/Cargo.toml new file mode 100644 index 00000000000000..500476526018d8 --- /dev/null +++ b/drivers/char/rust_example/Cargo.toml @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0 + +# No need for details like `authors` here -- that goes in the modinfo +# via the `kernel_module!` macro +[package] +name = "rust_example" +version = "0.1.0" +edition = "2018" +publish = false + +[lib] +crate-type = ["staticlib"] + +[dependencies] +kernel = { path = "../../../rust/kernel" } + diff --git a/drivers/char/rust_example/Makefile b/drivers/char/rust_example/Makefile new file mode 100644 index 00000000000000..f8847c8cad82c9 --- /dev/null +++ b/drivers/char/rust_example/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_RUST_EXAMPLE) += rust_example.o diff --git a/drivers/char/rust_example/src/lib.rs b/drivers/char/rust_example/src/lib.rs new file mode 100644 index 00000000000000..56562b840d9c77 --- /dev/null +++ b/drivers/char/rust_example/src/lib.rs @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0 + +#![no_std] +#![feature(global_asm)] + +use kernel::prelude::*; + +struct RustExample { + message: String, +} + +impl KernelModule for RustExample { + fn init() -> KernelResult { + println!("Rust Example (init)"); + println!("Am I built-in? {}", !cfg!(MODULE)); + Ok(RustExample { + message: "on the heap!".to_owned(), + }) + } +} + +impl Drop for RustExample { + fn drop(&mut self) { + println!("My message is {}", self.message); + println!("Rust Example (exit)"); + } +} + +kernel_module!( + RustExample, + author: b"Rust for Linux Contributors", + description: b"An example kernel module written in Rust", + license: b"GPL v2" +); diff --git a/include/linux/kallsyms.h b/include/linux/kallsyms.h index 481273f0c72d42..d26c41520ad5bf 100644 --- a/include/linux/kallsyms.h +++ b/include/linux/kallsyms.h @@ -14,7 +14,7 @@ #include -#define KSYM_NAME_LEN 128 +#define KSYM_NAME_LEN 256 #define KSYM_SYMBOL_LEN (sizeof("%s+%#lx/%#lx [%s]") + (KSYM_NAME_LEN - 1) + \ 2*(BITS_PER_LONG*3/10) + (MODULE_NAME_LEN - 1) + 1) diff --git a/init/Kconfig b/init/Kconfig index d6a0b31b13dc9d..ab00f9ae81f364 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -47,6 +47,19 @@ config CLANG_VERSION int default $(shell,$(srctree)/scripts/clang-version.sh $(CC)) +config HAS_RUST + def_bool $(success,$(RUSTC) --version && $(CARGO) --version) + +config RUSTC_VERSION + depends on HAS_RUST + int + default $(shell,$(srctree)/scripts/rust-version.sh $(RUSTC)) + +config CARGO_VERSION + depends on HAS_RUST + int + default $(shell,$(srctree)/scripts/rust-version.sh $(CARGO)) + config CC_CAN_LINK bool default $(success,$(srctree)/scripts/cc-can-link.sh $(CC) $(CLANG_FLAGS) $(m64-flag)) if 64BIT diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c index f76fdb9255323d..c51f2ca5094e41 100644 --- a/kernel/livepatch/core.c +++ b/kernel/livepatch/core.c @@ -214,7 +214,7 @@ static int klp_resolve_symbols(Elf64_Shdr *sechdrs, const char *strtab, * we use the smallest/strictest upper bound possible (56, based on * the current definition of MODULE_NAME_LEN) to prevent overflows. */ - BUILD_BUG_ON(MODULE_NAME_LEN < 56 || KSYM_NAME_LEN != 128); + BUILD_BUG_ON(MODULE_NAME_LEN < 56 || KSYM_NAME_LEN != 256); relas = (Elf_Rela *) relasec->sh_addr; /* For each rela in this klp relocation section */ @@ -228,7 +228,7 @@ static int klp_resolve_symbols(Elf64_Shdr *sechdrs, const char *strtab, /* Format: .klp.sym.sym_objname.sym_name,sympos */ cnt = sscanf(strtab + sym->st_name, - ".klp.sym.%55[^.].%127[^,],%lu", + ".klp.sym.%55[^.].%255[^,],%lu", sym_objname, sym_name, &sympos); if (cnt != 3) { pr_err("symbol %s has an incorrectly formatted name\n", diff --git a/rust/Makefile b/rust/Makefile new file mode 100644 index 00000000000000..02f2459083e4ec --- /dev/null +++ b/rust/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_HAS_RUST) = helpers.o + diff --git a/rust/helpers.c b/rust/helpers.c new file mode 100644 index 00000000000000..1efb4776b9e843 --- /dev/null +++ b/rust/helpers.c @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include + +void rust_helper_BUG(void) +{ + BUG(); +} +EXPORT_SYMBOL(rust_helper_BUG); + +int rust_helper_access_ok(const void __user *addr, unsigned long n) +{ + return access_ok(addr, n); +} +EXPORT_SYMBOL(rust_helper_access_ok); + +// See https://github.com/rust-lang/rust-bindgen/issues/1671 +static_assert(__builtin_types_compatible_p(size_t, uintptr_t), + "size_t must match uintptr_t, what architecture is this??"); diff --git a/rust/kernel/Cargo.toml b/rust/kernel/Cargo.toml new file mode 100644 index 00000000000000..d7f6b5aa375dee --- /dev/null +++ b/rust/kernel/Cargo.toml @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0 + +[package] +name = "kernel" +version = "0.1.0" +authors = ["Rust for Linux Contributors"] +edition = "2018" +publish = false + +[dependencies] +bitflags = "1" + +[build-dependencies] +bindgen = "0.54" +shlex = { path = "../shlex" } + diff --git a/rust/kernel/build.rs b/rust/kernel/build.rs new file mode 100644 index 00000000000000..b9085a6367b407 --- /dev/null +++ b/rust/kernel/build.rs @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0 + +use std::path::PathBuf; +use std::env; + +const INCLUDED_TYPES: &[&str] = &["file_system_type", "mode_t", "umode_t", "ctl_table"]; +const INCLUDED_FUNCTIONS: &[&str] = &[ + "cdev_add", + "cdev_init", + "cdev_del", + "register_filesystem", + "unregister_filesystem", + "krealloc", + "kfree", + "mount_nodev", + "kill_litter_super", + "register_sysctl", + "unregister_sysctl_table", + "access_ok", + "_copy_to_user", + "_copy_from_user", + "alloc_chrdev_region", + "unregister_chrdev_region", + "wait_for_random_bytes", + "get_random_bytes", + "rng_is_initialized", + "printk", + "add_device_randomness", +]; +const INCLUDED_VARS: &[&str] = &[ + "EINVAL", + "ENOMEM", + "ESPIPE", + "EFAULT", + "EAGAIN", + "__this_module", + "FS_REQUIRES_DEV", + "FS_BINARY_MOUNTDATA", + "FS_HAS_SUBTYPE", + "FS_USERNS_MOUNT", + "FS_RENAME_DOES_D_MOVE", + "BINDINGS_GFP_KERNEL", + "KERN_INFO", + "VERIFY_WRITE", + "LINUX_VERSION_CODE", + "SEEK_SET", + "SEEK_CUR", + "SEEK_END", + "O_NONBLOCK", +]; +const OPAQUE_TYPES: &[&str] = &[ + // These need to be opaque because they're both packed and aligned, which rustc + // doesn't support yet. See https://github.com/rust-lang/rust/issues/59154 + // and https://github.com/rust-lang/rust-bindgen/issues/1538 + "desc_struct", + "xregs_state", +]; + +// Takes the CFLAGS from the kernel Makefile and changes all the include paths to be absolute +// instead of relative. +fn prepare_cflags(cflags: &str, kernel_dir: &str) -> Vec { + let cflag_parts = shlex::split(&cflags).unwrap(); + let mut cflag_iter = cflag_parts.iter(); + let mut kernel_args = vec![]; + while let Some(arg) = cflag_iter.next() { + // TODO: bindgen complains + if arg.starts_with("-Wp,-MMD") { + continue; + } + + if arg.starts_with("-I") && !arg.starts_with("-I/") { + kernel_args.push(format!("-I{}/{}", kernel_dir, &arg[2..])); + } else if arg == "-include" { + kernel_args.push(arg.to_string()); + let include_path = cflag_iter.next().unwrap(); + if include_path.starts_with('/') { + kernel_args.push(include_path.to_string()); + } else { + kernel_args.push(format!("{}/{}", kernel_dir, include_path)); + } + } else { + kernel_args.push(arg.to_string()); + } + } + kernel_args +} + +fn main() { + println!("cargo:rerun-if-env-changed=CC"); + println!("cargo:rerun-if-env-changed=RUST_BINDGEN_CFLAGS"); + + let kernel_dir = "../../"; + let cflags = env::var("RUST_BINDGEN_CFLAGS") + .expect("Must be invoked from kernel makefile"); + + let kernel_args = prepare_cflags(&cflags, &kernel_dir); + + let target = env::var("TARGET").unwrap(); + + let mut builder = bindgen::Builder::default() + .use_core() + .ctypes_prefix("c_types") + .derive_default(true) + .size_t_is_usize(true) + .rustfmt_bindings(true); + + builder = builder.clang_arg(format!("--target={}", target)); + for arg in kernel_args.iter() { + builder = builder.clang_arg(arg.clone()); + } + + println!("cargo:rerun-if-changed=src/bindings_helper.h"); + builder = builder.header("src/bindings_helper.h"); + + for t in INCLUDED_TYPES { + builder = builder.whitelist_type(t); + } + for f in INCLUDED_FUNCTIONS { + builder = builder.whitelist_function(f); + } + for v in INCLUDED_VARS { + builder = builder.whitelist_var(v); + } + for t in OPAQUE_TYPES { + builder = builder.opaque_type(t); + } + let bindings = builder.generate().expect("Unable to generate bindings"); + + let out_path = PathBuf::from("src/bindings_gen.rs"); + bindings + .write_to_file(out_path) + .expect("Couldn't write bindings!"); +} diff --git a/rust/kernel/src/.gitignore b/rust/kernel/src/.gitignore new file mode 100644 index 00000000000000..61552b03b12d41 --- /dev/null +++ b/rust/kernel/src/.gitignore @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 + +bindings_gen.rs diff --git a/rust/kernel/src/allocator.rs b/rust/kernel/src/allocator.rs new file mode 100644 index 00000000000000..27647be92b5134 --- /dev/null +++ b/rust/kernel/src/allocator.rs @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0 + +use core::alloc::{GlobalAlloc, Layout}; +use core::ptr; + +use crate::bindings; +use crate::c_types; + +pub struct KernelAllocator; + +unsafe impl GlobalAlloc for KernelAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + // krealloc is used instead of kmalloc because kmalloc is an inline function and can't be + // bound to as a result + bindings::krealloc(ptr::null(), layout.size(), bindings::GFP_KERNEL) as *mut u8 + } + + unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { + bindings::kfree(ptr as *const c_types::c_void); + } +} + +#[alloc_error_handler] +fn oom(_layout: Layout) -> ! { + panic!("Out of memory!"); +} diff --git a/rust/kernel/src/bindings.rs b/rust/kernel/src/bindings.rs new file mode 100644 index 00000000000000..cfb004a6d7861c --- /dev/null +++ b/rust/kernel/src/bindings.rs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0 + +#[allow( + clippy::all, + non_camel_case_types, + non_upper_case_globals, + non_snake_case, + improper_ctypes +)] +mod bindings_raw { + use crate::c_types; + include!("bindings_gen.rs"); +} +pub use bindings_raw::*; + +pub const GFP_KERNEL: gfp_t = BINDINGS_GFP_KERNEL; diff --git a/rust/kernel/src/bindings_helper.h b/rust/kernel/src/bindings_helper.h new file mode 100644 index 00000000000000..b5b0b2e1d18d88 --- /dev/null +++ b/rust/kernel/src/bindings_helper.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#include +#include +#include +#include +#include +#include +#include + +// bindgen gets confused at certain things +const gfp_t BINDINGS_GFP_KERNEL = GFP_KERNEL; diff --git a/rust/kernel/src/c_types.rs b/rust/kernel/src/c_types.rs new file mode 100644 index 00000000000000..35776920c99d52 --- /dev/null +++ b/rust/kernel/src/c_types.rs @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0 + +#![allow(non_camel_case_types)] + +#[cfg(target_arch = "x86_64")] +mod c { + use core::ffi; + + pub type c_int = i32; + pub type c_char = i8; + pub type c_long = i64; + pub type c_longlong = i64; + pub type c_short = i16; + pub type c_uchar = u8; + pub type c_uint = u32; + pub type c_ulong = u64; + pub type c_ulonglong = u64; + pub type c_ushort = u16; + pub type c_schar = i8; + pub type c_size_t = usize; + pub type c_ssize_t = isize; + pub type c_void = ffi::c_void; +} + +pub use c::*; diff --git a/rust/kernel/src/chrdev.rs b/rust/kernel/src/chrdev.rs new file mode 100644 index 00000000000000..4606a7bd5a733d --- /dev/null +++ b/rust/kernel/src/chrdev.rs @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0 + +use core::convert::TryInto; +use core::mem; +use core::ops::Range; + +use alloc::boxed::Box; +use alloc::vec; +use alloc::vec::Vec; + +use crate::bindings; +use crate::c_types; +use crate::error::{Error, KernelResult}; +use crate::file_operations; +use crate::types::CStr; + +pub fn builder(name: &'static CStr, minors: Range) -> KernelResult { + Ok(Builder { + name, + minors, + file_ops: vec![], + }) +} + +pub struct Builder { + name: &'static CStr, + minors: Range, + file_ops: Vec<&'static bindings::file_operations>, +} + +impl Builder { + pub fn register_device(mut self) -> Builder { + if self.file_ops.len() >= self.minors.len() { + panic!("More devices registered than minor numbers allocated.") + } + self.file_ops + .push(&file_operations::FileOperationsVtable::::VTABLE); + self + } + + pub fn build(self) -> KernelResult { + let mut dev: bindings::dev_t = 0; + let res = unsafe { + bindings::alloc_chrdev_region( + &mut dev, + self.minors.start.into(), + self.minors.len().try_into()?, + self.name.as_ptr() as *const c_types::c_char, + ) + }; + if res != 0 { + return Err(Error::from_kernel_errno(res)); + } + + // Turn this into a boxed slice immediately because the kernel stores pointers into it, and + // so that data should never be moved. + let mut cdevs = vec![unsafe { mem::zeroed() }; self.file_ops.len()].into_boxed_slice(); + for (i, file_op) in self.file_ops.iter().enumerate() { + unsafe { + bindings::cdev_init(&mut cdevs[i], *file_op); + // TODO: proper `THIS_MODULE` handling + cdevs[i].owner = core::ptr::null_mut(); + let rc = bindings::cdev_add(&mut cdevs[i], dev + i as bindings::dev_t, 1); + if rc != 0 { + // Clean up the ones that were allocated. + for j in 0..=i { + bindings::cdev_del(&mut cdevs[j]); + } + bindings::unregister_chrdev_region(dev, self.minors.len() as _); + return Err(Error::from_kernel_errno(rc)); + } + } + } + + Ok(Registration { + dev, + count: self.minors.len(), + cdevs, + }) + } +} + +pub struct Registration { + dev: bindings::dev_t, + count: usize, + cdevs: Box<[bindings::cdev]>, +} + +// This is safe because Registration doesn't actually expose any methods. +unsafe impl Sync for Registration {} + +impl Drop for Registration { + fn drop(&mut self) { + unsafe { + for dev in self.cdevs.iter_mut() { + bindings::cdev_del(dev); + } + bindings::unregister_chrdev_region(self.dev, self.count as _); + } + } +} diff --git a/rust/kernel/src/error.rs b/rust/kernel/src/error.rs new file mode 100644 index 00000000000000..95e322e39a88e7 --- /dev/null +++ b/rust/kernel/src/error.rs @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0 + +use core::num::TryFromIntError; + +use crate::bindings; +use crate::c_types; + +pub struct Error(c_types::c_int); + +impl Error { + pub const EINVAL: Self = Error(-(bindings::EINVAL as i32)); + pub const ENOMEM: Self = Error(-(bindings::ENOMEM as i32)); + pub const EFAULT: Self = Error(-(bindings::EFAULT as i32)); + pub const ESPIPE: Self = Error(-(bindings::ESPIPE as i32)); + pub const EAGAIN: Self = Error(-(bindings::EAGAIN as i32)); + + pub fn from_kernel_errno(errno: c_types::c_int) -> Error { + Error(errno) + } + + pub fn to_kernel_errno(&self) -> c_types::c_int { + self.0 + } +} + +impl From for Error { + fn from(_: TryFromIntError) -> Error { + Error::EINVAL + } +} + +pub type KernelResult = Result; diff --git a/rust/kernel/src/file_operations.rs b/rust/kernel/src/file_operations.rs new file mode 100644 index 00000000000000..100fb62281e946 --- /dev/null +++ b/rust/kernel/src/file_operations.rs @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: GPL-2.0 + +use core::convert::{TryFrom, TryInto}; +use core::{marker, mem, ptr}; + +use alloc::boxed::Box; + +use crate::bindings; +use crate::c_types; +use crate::error::{Error, KernelResult}; +use crate::user_ptr::{UserSlicePtr, UserSlicePtrReader, UserSlicePtrWriter}; + +bitflags::bitflags! { + pub struct FileFlags: c_types::c_uint { + const NONBLOCK = bindings::O_NONBLOCK; + } +} + +pub struct File { + ptr: *const bindings::file, +} + +impl File { + unsafe fn from_ptr(ptr: *const bindings::file) -> File { + File { ptr } + } + + pub fn pos(&self) -> u64 { + unsafe { (*self.ptr).f_pos as u64 } + } + + pub fn flags(&self) -> FileFlags { + FileFlags::from_bits_truncate(unsafe { (*self.ptr).f_flags }) + } +} + +// Matches std::io::SeekFrom in the Rust stdlib +pub enum SeekFrom { + Start(u64), + End(i64), + Current(i64), +} + +unsafe extern "C" fn open_callback( + _inode: *mut bindings::inode, + file: *mut bindings::file, +) -> c_types::c_int { + let f = match T::open() { + Ok(f) => Box::new(f), + Err(e) => return e.to_kernel_errno(), + }; + (*file).private_data = Box::into_raw(f) as *mut c_types::c_void; + 0 +} + +unsafe extern "C" fn read_callback( + file: *mut bindings::file, + buf: *mut c_types::c_char, + len: c_types::c_size_t, + offset: *mut bindings::loff_t, +) -> c_types::c_ssize_t { + let mut data = match UserSlicePtr::new(buf as *mut c_types::c_void, len) { + Ok(ptr) => ptr.writer(), + Err(e) => return e.to_kernel_errno().try_into().unwrap(), + }; + let f = &*((*file).private_data as *const T); + // No FMODE_UNSIGNED_OFFSET support, so offset must be in [0, 2^63). + // See discussion in #113 + let positive_offset = match (*offset).try_into() { + Ok(v) => v, + Err(_) => return Error::EINVAL.to_kernel_errno().try_into().unwrap(), + }; + let read = T::READ.unwrap(); + match read(f, &File::from_ptr(file), &mut data, positive_offset) { + Ok(()) => { + let written = len - data.len(); + (*offset) += bindings::loff_t::try_from(written).unwrap(); + written.try_into().unwrap() + } + Err(e) => e.to_kernel_errno().try_into().unwrap(), + } +} + +unsafe extern "C" fn write_callback( + file: *mut bindings::file, + buf: *const c_types::c_char, + len: c_types::c_size_t, + offset: *mut bindings::loff_t, +) -> c_types::c_ssize_t { + let mut data = match UserSlicePtr::new(buf as *mut c_types::c_void, len) { + Ok(ptr) => ptr.reader(), + Err(e) => return e.to_kernel_errno().try_into().unwrap(), + }; + let f = &*((*file).private_data as *const T); + // No FMODE_UNSIGNED_OFFSET support, so offset must be in [0, 2^63). + // See discussion in #113 + let positive_offset = match (*offset).try_into() { + Ok(v) => v, + Err(_) => return Error::EINVAL.to_kernel_errno().try_into().unwrap(), + }; + let write = T::WRITE.unwrap(); + match write(f, &mut data, positive_offset) { + Ok(()) => { + let read = len - data.len(); + (*offset) += bindings::loff_t::try_from(read).unwrap(); + read.try_into().unwrap() + } + Err(e) => e.to_kernel_errno().try_into().unwrap(), + } +} + +unsafe extern "C" fn release_callback( + _inode: *mut bindings::inode, + file: *mut bindings::file, +) -> c_types::c_int { + let ptr = mem::replace(&mut (*file).private_data, ptr::null_mut()); + drop(Box::from_raw(ptr as *mut T)); + 0 +} + +unsafe extern "C" fn llseek_callback( + file: *mut bindings::file, + offset: bindings::loff_t, + whence: c_types::c_int, +) -> bindings::loff_t { + let off = match whence as u32 { + bindings::SEEK_SET => match offset.try_into() { + Ok(v) => SeekFrom::Start(v), + Err(_) => return Error::EINVAL.to_kernel_errno().into(), + }, + bindings::SEEK_CUR => SeekFrom::Current(offset), + bindings::SEEK_END => SeekFrom::End(offset), + _ => return Error::EINVAL.to_kernel_errno().into(), + }; + let f = &*((*file).private_data as *const T); + let seek = T::SEEK.unwrap(); + match seek(f, &File::from_ptr(file), off) { + Ok(off) => off as bindings::loff_t, + Err(e) => e.to_kernel_errno().into(), + } +} + +pub(crate) struct FileOperationsVtable(marker::PhantomData); + +impl FileOperationsVtable { + pub(crate) const VTABLE: bindings::file_operations = bindings::file_operations { + open: Some(open_callback::), + release: Some(release_callback::), + read: if let Some(_) = T::READ { + Some(read_callback::) + } else { + None + }, + write: if let Some(_) = T::WRITE { + Some(write_callback::) + } else { + None + }, + llseek: if let Some(_) = T::SEEK { + Some(llseek_callback::) + } else { + None + }, + + check_flags: None, + compat_ioctl: None, + copy_file_range: None, + fallocate: None, + fadvise: None, + fasync: None, + flock: None, + flush: None, + fsync: None, + get_unmapped_area: None, + iterate: None, + iterate_shared: None, + iopoll: None, + lock: None, + mmap: None, + mmap_supported_flags: 0, + owner: ptr::null_mut(), + poll: None, + read_iter: None, + remap_file_range: None, + sendpage: None, + setlease: None, + show_fdinfo: None, + splice_read: None, + splice_write: None, + unlocked_ioctl: None, + write_iter: None, + }; +} + +pub type ReadFn = Option KernelResult<()>>; +pub type WriteFn = Option KernelResult<()>>; +pub type SeekFn = Option KernelResult>; + +/// `FileOperations` corresponds to the kernel's `struct file_operations`. You +/// implement this trait whenever you'd create a `struct file_operations`. +/// File descriptors may be used from multiple threads (or processes) +/// concurrently, so your type must be `Sync`. +pub trait FileOperations: Sync + Sized { + /// Creates a new instance of this file. Corresponds to the `open` function + /// pointer in `struct file_operations`. + fn open() -> KernelResult; + + /// Reads data from this file to userspace. Corresponds to the `read` + /// function pointer in `struct file_operations`. + const READ: ReadFn = None; + + /// Writes data from userspace o this file. Corresponds to the `write` + /// function pointer in `struct file_operations`. + const WRITE: WriteFn = None; + + /// Changes the position of the file. Corresponds to the `llseek` function + /// pointer in `struct file_operations`. + const SEEK: SeekFn = None; +} diff --git a/rust/kernel/src/filesystem.rs b/rust/kernel/src/filesystem.rs new file mode 100644 index 00000000000000..a5b7774292f1b2 --- /dev/null +++ b/rust/kernel/src/filesystem.rs @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0 + +// TODO: `filesystem` is really incomplete -- deletion to be considered + +use alloc::boxed::Box; +use core::default::Default; +use core::marker; + +use crate::bindings; +use crate::c_types; +use crate::error; +use crate::types::CStr; + +pub struct Registration { + _phantom: marker::PhantomData, + ptr: Box, +} + +// This is safe because Registration doesn't actually expose any methods. +unsafe impl Sync for Registration where T: FileSystem {} + +impl Drop for Registration { + fn drop(&mut self) { + unsafe { bindings::unregister_filesystem(&mut *self.ptr) }; + } +} + +pub trait FileSystem: Sync { + const NAME: &'static CStr; + const FLAGS: FileSystemFlags; +} + +bitflags::bitflags! { + pub struct FileSystemFlags: c_types::c_int { + const REQUIRES_DEV = bindings::FS_REQUIRES_DEV as c_types::c_int; + const BINARY_MOUNTDATA = bindings::FS_BINARY_MOUNTDATA as c_types::c_int; + const HAS_SUBTYPE = bindings::FS_HAS_SUBTYPE as c_types::c_int; + const USERNS_MOUNT = bindings::FS_USERNS_MOUNT as c_types::c_int; + const RENAME_DOES_D_MOVE = bindings::FS_RENAME_DOES_D_MOVE as c_types::c_int; + } +} + +extern "C" fn fill_super_callback( + _sb: *mut bindings::super_block, + _data: *mut c_types::c_void, + _silent: c_types::c_int, +) -> c_types::c_int { + // T::fill_super(...) + // This should actually create an object that gets dropped by + // file_system_registration::kill_sb. You can point to it with + // sb->s_fs_info. + unimplemented!(); +} + +extern "C" fn mount_callback( + fs_type: *mut bindings::file_system_type, + flags: c_types::c_int, + _dev_name: *const c_types::c_char, + data: *mut c_types::c_void, +) -> *mut bindings::dentry { + unsafe { bindings::mount_nodev(fs_type, flags, data, Some(fill_super_callback::)) } +} + +pub fn register() -> error::KernelResult> { + let mut fs_registration = Registration { + ptr: Box::new(bindings::file_system_type { + name: T::NAME.as_ptr() as *const i8, + // TODO: proper `THIS_MODULE` handling + owner: core::ptr::null_mut(), + fs_flags: T::FLAGS.bits(), + mount: Some(mount_callback::), + kill_sb: Some(bindings::kill_litter_super), + + ..Default::default() + }), + _phantom: marker::PhantomData, + }; + let result = unsafe { bindings::register_filesystem(&mut *fs_registration.ptr) }; + if result != 0 { + return Err(error::Error::from_kernel_errno(result)); + } + + Ok(fs_registration) +} diff --git a/rust/kernel/src/lib.rs b/rust/kernel/src/lib.rs new file mode 100644 index 00000000000000..18d553c094ffaf --- /dev/null +++ b/rust/kernel/src/lib.rs @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! The `kernel` crate + +#![no_std] +#![feature(allocator_api, alloc_error_handler, const_raw_ptr_deref)] + +extern crate alloc; + +use core::panic::PanicInfo; + +mod allocator; +pub mod bindings; +pub mod c_types; +pub mod chrdev; +mod error; +pub mod file_operations; +pub mod filesystem; +pub mod prelude; +pub mod printk; +pub mod random; +pub mod sysctl; +mod types; +pub mod user_ptr; + +pub use crate::error::{Error, KernelResult}; +pub use crate::types::{CStr, Mode}; + +/// Declares the entrypoint for a kernel module. The first argument should be a type which +/// implements the [`KernelModule`] trait. Also accepts various forms of kernel metadata. +/// +/// Example: +/// ```rust,no_run +/// use kernel::prelude::*; +/// +/// struct MyKernelModule; +/// impl KernelModule for MyKernelModule { +/// fn init() -> KernelResult { +/// Ok(MyKernelModule) +/// } +/// } +/// +/// kernel_module!( +/// MyKernelModule, +/// author: b"Rust for Linux Contributors", +/// description: b"My very own kernel module!", +/// license: b"GPL" +/// ); +#[macro_export] +macro_rules! kernel_module { + ($module:ty, $($name:ident : $value:expr),*) => { + static mut __MOD: Option<$module> = None; + + // Built-in modules are initialized through an initcall pointer + // + // TODO: should we compile a C file on the fly to avoid duplication? + #[cfg(not(MODULE))] + #[cfg(not(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS))] + #[link_section = ".initcall6.init"] + #[used] + pub static __initcall: extern "C" fn() -> $crate::c_types::c_int = init_module; + + #[cfg(not(MODULE))] + #[cfg(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS)] + global_asm!( + r#".section ".initcall6.init", "a" + __initcall: + .long init_module - . + .previous + "# + ); + + // TODO: pass the kernel module name here to generate a unique, + // helpful symbol name (the name would also useful for the `modinfo` + // issue below). + #[no_mangle] + pub extern "C" fn init_module() -> $crate::c_types::c_int { + match <$module as $crate::KernelModule>::init() { + Ok(m) => { + unsafe { + __MOD = Some(m); + } + return 0; + } + Err(e) => { + return e.to_kernel_errno(); + } + } + } + + #[no_mangle] + pub extern "C" fn cleanup_module() { + unsafe { + // Invokes drop() on __MOD, which should be used for cleanup. + __MOD = None; + } + } + + $( + $crate::kernel_module!(@attribute $name, $value); + )* + }; + + // TODO: The modinfo attributes below depend on the compiler placing + // the variables in order in the .modinfo section, so that you end up + // with b"key=value\0" in order in the section. This is a reasonably + // standard trick in C, but I'm not sure that rustc guarantees it. + // + // Ideally we'd be able to use concat_bytes! + stringify_bytes! + + // some way of turning a string literal (or at least a string + // literal token) into a bytes literal, and get a single static + // [u8; * N] with the whole thing, but those don't really exist yet. + // Most of the alternatives (e.g. .as_bytes() as a const fn) give + // you a pointer, not an array, which isn't right. + + // TODO: `modules.builtin.modinfo` etc. is missing the prefix (module name) + (@attribute author, $value:expr) => { + #[link_section = ".modinfo"] + #[used] + pub static AUTHOR_KEY: [u8; 7] = *b"author="; + #[link_section = ".modinfo"] + #[used] + pub static AUTHOR_VALUE: [u8; $value.len()] = *$value; + #[link_section = ".modinfo"] + #[used] + pub static AUTHOR_NUL: [u8; 1] = *b"\0"; + }; + + (@attribute description, $value:expr) => { + #[link_section = ".modinfo"] + #[used] + pub static DESCRIPTION_KEY: [u8; 12] = *b"description="; + #[link_section = ".modinfo"] + #[used] + pub static DESCRIPTION_VALUE: [u8; $value.len()] = *$value; + #[link_section = ".modinfo"] + #[used] + pub static DESCRIPTION_NUL: [u8; 1] = *b"\0"; + }; + + (@attribute license, $value:expr) => { + #[link_section = ".modinfo"] + #[used] + pub static LICENSE_KEY: [u8; 8] = *b"license="; + #[link_section = ".modinfo"] + #[used] + pub static LICENSE_VALUE: [u8; $value.len()] = *$value; + #[link_section = ".modinfo"] + #[used] + pub static LICENSE_NUL: [u8; 1] = *b"\0"; + }; +} + +/// KernelModule is the top level entrypoint to implementing a kernel module. Your kernel module +/// should implement the `init` method on it, which maps to the `module_init` macro in Linux C API. +/// You can use this method to do whatever setup or registration your module should do. For any +/// teardown or cleanup operations, your type may implement [`Drop`]. +/// +/// [`Drop`]: https://doc.rust-lang.org/stable/core/ops/trait.Drop.html +pub trait KernelModule: Sized + Sync { + fn init() -> KernelResult; +} + +extern "C" { + fn rust_helper_BUG() -> !; +} + +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + unsafe { + rust_helper_BUG(); + } +} + +#[global_allocator] +static ALLOCATOR: allocator::KernelAllocator = allocator::KernelAllocator; diff --git a/rust/kernel/src/prelude.rs b/rust/kernel/src/prelude.rs new file mode 100644 index 00000000000000..d88baa99c902dd --- /dev/null +++ b/rust/kernel/src/prelude.rs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! The `kernel` prelude + +pub use alloc::{ + string::String, + borrow::ToOwned, +}; + +pub use super::{ + kernel_module, + println, + KernelResult, + KernelModule, +}; + diff --git a/rust/kernel/src/printk.rs b/rust/kernel/src/printk.rs new file mode 100644 index 00000000000000..1a27c034258a36 --- /dev/null +++ b/rust/kernel/src/printk.rs @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0 + +use core::cmp; +use core::fmt; + +use crate::bindings; +use crate::c_types::c_int; + +#[doc(hidden)] +pub fn printk(s: &[u8]) { + // Don't copy the trailing NUL from `KERN_INFO`. + let mut fmt_str = [0; bindings::KERN_INFO.len() - 1 + b"%.*s\0".len()]; + fmt_str[..bindings::KERN_INFO.len() - 1] + .copy_from_slice(&bindings::KERN_INFO[..bindings::KERN_INFO.len() - 1]); + fmt_str[bindings::KERN_INFO.len() - 1..].copy_from_slice(b"%.*s\0"); + + // TODO: I believe printk never fails + unsafe { bindings::printk(fmt_str.as_ptr() as _, s.len() as c_int, s.as_ptr()) }; +} + +// From kernel/print/printk.c +const LOG_LINE_MAX: usize = 1024 - 32; + +#[doc(hidden)] +pub struct LogLineWriter { + data: [u8; LOG_LINE_MAX], + pos: usize, +} + +#[allow(clippy::new_without_default)] +impl LogLineWriter { + pub fn new() -> LogLineWriter { + LogLineWriter { + data: [0u8; LOG_LINE_MAX], + pos: 0, + } + } + + pub fn as_bytes(&self) -> &[u8] { + &self.data[..self.pos] + } +} + +impl fmt::Write for LogLineWriter { + fn write_str(&mut self, s: &str) -> fmt::Result { + let copy_len = cmp::min(LOG_LINE_MAX - self.pos, s.as_bytes().len()); + self.data[self.pos..self.pos + copy_len].copy_from_slice(&s.as_bytes()[..copy_len]); + self.pos += copy_len; + Ok(()) + } +} + +/// [`println!`] functions the same as it does in `std`, except instead of +/// printing to `stdout`, it writes to the kernel console at the `KERN_INFO` +/// level. +/// +/// [`println!`]: https://doc.rust-lang.org/stable/std/macro.println.html +#[macro_export] +macro_rules! println { + () => ({ + $crate::printk::printk("\n".as_bytes()); + }); + ($fmt:expr) => ({ + $crate::printk::printk(concat!($fmt, "\n").as_bytes()); + }); + ($fmt:expr, $($arg:tt)*) => ({ + use ::core::fmt; + let mut writer = $crate::printk::LogLineWriter::new(); + let _ = fmt::write(&mut writer, format_args!(concat!($fmt, "\n"), $($arg)*)).unwrap(); + $crate::printk::printk(writer.as_bytes()); + }); +} diff --git a/rust/kernel/src/random.rs b/rust/kernel/src/random.rs new file mode 100644 index 00000000000000..bd80c835749dac --- /dev/null +++ b/rust/kernel/src/random.rs @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0 + +use core::convert::TryInto; + +use crate::{bindings, c_types, error}; + +/// Fills `dest` with random bytes generated from the kernel's CSPRNG. Ensures +/// that the CSPRNG has been seeded before generating any random bytes, and +/// will block until it's ready. +pub fn getrandom(dest: &mut [u8]) -> error::KernelResult<()> { + let res = unsafe { bindings::wait_for_random_bytes() }; + if res != 0 { + return Err(error::Error::from_kernel_errno(res)); + } + + unsafe { + bindings::get_random_bytes( + dest.as_mut_ptr() as *mut c_types::c_void, + dest.len().try_into()?, + ); + } + Ok(()) +} + +/// Fills `dest` with random bytes generated from the kernel's CSPRNG. If the +/// CSPRNG is not yet seeded, returns an `Err(EAGAIN)` immediately. +pub fn getrandom_nonblock(dest: &mut [u8]) -> error::KernelResult<()> { + if !unsafe { bindings::rng_is_initialized() } { + return Err(error::Error::EAGAIN); + } + getrandom(dest) +} + +/// Contributes the contents of `data` to the kernel's entropy pool. Does _not_ +/// credit the kernel entropy counter though. +pub fn add_randomness(data: &[u8]) { + unsafe { + bindings::add_device_randomness( + data.as_ptr() as *const c_types::c_void, + data.len().try_into().unwrap(), + ); + } +} diff --git a/rust/kernel/src/sysctl.rs b/rust/kernel/src/sysctl.rs new file mode 100644 index 00000000000000..654b7122ba3bac --- /dev/null +++ b/rust/kernel/src/sysctl.rs @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0 + +use alloc::boxed::Box; +use alloc::vec; +use core::mem; +use core::ptr; +use core::sync::atomic; + +use crate::bindings; +use crate::c_types; +use crate::error; +use crate::types; +use crate::user_ptr::{UserSlicePtr, UserSlicePtrWriter}; + +pub trait SysctlStorage: Sync { + fn store_value(&self, data: &[u8]) -> (usize, error::KernelResult<()>); + fn read_value(&self, data: &mut UserSlicePtrWriter) -> (usize, error::KernelResult<()>); +} + +fn trim_whitespace(mut data: &[u8]) -> &[u8] { + while !data.is_empty() && (data[0] == b' ' || data[0] == b'\t' || data[0] == b'\n') { + data = &data[1..]; + } + while !data.is_empty() + && (data[data.len() - 1] == b' ' + || data[data.len() - 1] == b'\t' + || data[data.len() - 1] == b'\n') + { + data = &data[..data.len() - 1]; + } + data +} + +impl SysctlStorage for &T +where + T: SysctlStorage, +{ + fn store_value(&self, data: &[u8]) -> (usize, error::KernelResult<()>) { + (*self).store_value(data) + } + + fn read_value(&self, data: &mut UserSlicePtrWriter) -> (usize, error::KernelResult<()>) { + (*self).read_value(data) + } +} + +impl SysctlStorage for atomic::AtomicBool { + fn store_value(&self, data: &[u8]) -> (usize, error::KernelResult<()>) { + let result = match trim_whitespace(data) { + b"0" => { + self.store(false, atomic::Ordering::Relaxed); + Ok(()) + } + b"1" => { + self.store(true, atomic::Ordering::Relaxed); + Ok(()) + } + _ => Err(error::Error::EINVAL), + }; + (data.len(), result) + } + + fn read_value(&self, data: &mut UserSlicePtrWriter) -> (usize, error::KernelResult<()>) { + let value = if self.load(atomic::Ordering::Relaxed) { + b"1\n" + } else { + b"0\n" + }; + (value.len(), data.write(value)) + } +} + +pub struct Sysctl { + inner: Box, + // Responsible for keeping the ctl_table alive. + _table: Box<[bindings::ctl_table]>, + header: *mut bindings::ctl_table_header, +} + +// This is safe because the only public method we have is get(), which returns +// &T, and T: Sync. Any new methods must adhere to this requirement. +unsafe impl Sync for Sysctl {} + +unsafe extern "C" fn proc_handler( + ctl: *mut bindings::ctl_table, + write: c_types::c_int, + buffer: *mut c_types::c_void, + len: *mut usize, + ppos: *mut bindings::loff_t, +) -> c_types::c_int { + // If we're reading from some offset other than the beginning of the file, + // return an empty read to signal EOF. + if *ppos != 0 && write == 0 { + *len = 0; + return 0; + } + + let data = match UserSlicePtr::new(buffer, *len) { + Ok(ptr) => ptr, + Err(e) => return e.to_kernel_errno(), + }; + let storage = &*((*ctl).data as *const T); + let (bytes_processed, result) = if write != 0 { + let data = match data.read_all() { + Ok(r) => r, + Err(e) => return e.to_kernel_errno(), + }; + storage.store_value(&data) + } else { + let mut writer = data.writer(); + storage.read_value(&mut writer) + }; + *len = bytes_processed; + *ppos += *len as bindings::loff_t; + match result { + Ok(()) => 0, + Err(e) => e.to_kernel_errno(), + } +} + +impl Sysctl { + pub fn register( + path: &'static types::CStr, + name: &'static types::CStr, + storage: T, + mode: types::Mode, + ) -> error::KernelResult> { + if name.contains('/') { + return Err(error::Error::EINVAL); + } + + let storage = Box::new(storage); + let mut table = vec![ + bindings::ctl_table { + procname: name.as_ptr() as *const i8, + mode: mode.as_int(), + data: &*storage as *const T as *mut c_types::c_void, + proc_handler: Some(proc_handler::), + + maxlen: 0, + child: ptr::null_mut(), + poll: ptr::null_mut(), + extra1: ptr::null_mut(), + extra2: ptr::null_mut(), + }, + unsafe { mem::zeroed() }, + ] + .into_boxed_slice(); + + let result = + unsafe { bindings::register_sysctl(path.as_ptr() as *const i8, table.as_mut_ptr()) }; + if result.is_null() { + return Err(error::Error::ENOMEM); + } + + Ok(Sysctl { + inner: storage, + _table: table, + header: result, + }) + } + + pub fn get(&self) -> &T { + &self.inner + } +} + +impl Drop for Sysctl { + fn drop(&mut self) { + unsafe { + bindings::unregister_sysctl_table(self.header); + } + self.header = ptr::null_mut(); + } +} diff --git a/rust/kernel/src/types.rs b/rust/kernel/src/types.rs new file mode 100644 index 00000000000000..ea5f8d7c2ebb03 --- /dev/null +++ b/rust/kernel/src/types.rs @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0 + +use core::ops::Deref; + +use crate::bindings; + +pub struct Mode(bindings::umode_t); + +impl Mode { + pub fn from_int(m: u16) -> Mode { + Mode(m) + } + + pub fn as_int(&self) -> u16 { + self.0 + } +} + +/// A string that is guaranteed to have exactly one NUL byte, which is at the +/// end. Used for interoperability with kernel APIs that take C strings. +#[repr(transparent)] +pub struct CStr(str); + +impl CStr { + /// Creates a new CStr from a str without performing any additional checks. + /// # Safety + /// + /// `data` _must_ end with a NUL byte, and should only have only a single + /// NUL byte, or the string will be truncated. + pub const unsafe fn new_unchecked(data: &str) -> &CStr { + &*(data as *const str as *const CStr) + } +} + +impl Deref for CStr { + type Target = str; + + fn deref(&self) -> &str { + &self.0 + } +} + +/// Creates a new `CStr` from a string literal. The string literal should not contain any NUL +/// bytes. Example usage: +/// ``` +/// const MY_CSTR: &CStr = cstr!("My awesome CStr!"); +/// ``` +#[macro_export] +macro_rules! cstr { + ($str:expr) => {{ + let s = concat!($str, "\x00"); + unsafe { $crate::CStr::new_unchecked(s) } + }}; +} diff --git a/rust/kernel/src/user_ptr.rs b/rust/kernel/src/user_ptr.rs new file mode 100644 index 00000000000000..19ecb711e24a7b --- /dev/null +++ b/rust/kernel/src/user_ptr.rs @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: GPL-2.0 + +use alloc::vec; +use alloc::vec::Vec; +use core::u32; + +use crate::bindings; +use crate::c_types; +use crate::error; + +extern "C" { + fn rust_helper_access_ok(addr: *const c_types::c_void, len: c_types::c_ulong) -> c_types::c_int; +} + +/// A reference to an area in userspace memory, which can be either +/// read-only or read-write. +/// +/// All methods on this struct are safe: invalid pointers return +/// `EFAULT`. Concurrent access, _including data races to/from userspace +/// memory_, is permitted, because fundamentally another userspace +/// thread / process could always be modifying memory at the same time +/// (in the same way that userspace Rust's std::io permits data races +/// with the contents of files on disk). In the presence of a race, the +/// exact byte values read/written are unspecified but the operation is +/// well-defined. Kernelspace code should validate its copy of data +/// after completing a read, and not expect that multiple reads of the +/// same address will return the same value. +/// +/// All APIs enforce the invariant that a given byte of memory from userspace +/// may only be read once. By pretenting double-fetches we avoid TOCTOU +/// vulnerabilities. This is accomplished by taking `self` by value to prevent +/// obtaining multiple readers on a given UserSlicePtr, and the readers only +/// permitting forward reads. +/// +/// Constructing a `UserSlicePtr` only checks that the range is in valid +/// userspace memory, and does not depend on the current process (and +/// can safely be constructed inside a kernel thread with no current +/// userspace process). Reads and writes wrap the kernel APIs +/// `copy_from_user` and `copy_to_user`, and check the memory map of the +/// current process. +pub struct UserSlicePtr(*mut c_types::c_void, usize); + +impl UserSlicePtr { + /// Construct a user slice from a raw pointer and a length in bytes. + /// + /// Checks that the provided range is within the legal area for + /// userspace memory, using `access_ok` (e.g., on i386, the range + /// must be within the first 3 gigabytes), but does not check that + /// the actual pages are mapped in the current process with + /// appropriate permissions. Those checks are handled in the read + /// and write methods. + /// + /// This is `unsafe` because if it is called within `set_fs(KERNEL_DS)` context then + /// `access_ok` will not do anything. As a result the only place you can safely use this is + /// with an `__user` pointer that was provided by the kernel. + pub(crate) unsafe fn new( + ptr: *mut c_types::c_void, + length: usize, + ) -> error::KernelResult { + if rust_helper_access_ok(ptr, length as c_types::c_ulong) == 0 { + return Err(error::Error::EFAULT); + } + Ok(UserSlicePtr(ptr, length)) + } + + /// Read the entirety of the user slice and return it in a `Vec`. + /// + /// Returns EFAULT if the address does not currently point to + /// mapped, readable memory. + pub fn read_all(self) -> error::KernelResult> { + self.reader().read_all() + } + + /// Construct a `UserSlicePtrReader` that can incrementally read + /// from the user slice. + pub fn reader(self) -> UserSlicePtrReader { + UserSlicePtrReader(self.0, self.1) + } + + /// Write the provided slice into the user slice. + /// + /// Returns EFAULT if the address does not currently point to + /// mapped, writable memory (in which case some data from before the + /// fault may be written), or `data` is larger than the user slice + /// (in which case no data is written). + pub fn write_all(self, data: &[u8]) -> error::KernelResult<()> { + self.writer().write(data) + } + + /// Construct a `UserSlicePtrWrite` that can incrementally write + /// into the user slice. + pub fn writer(self) -> UserSlicePtrWriter { + UserSlicePtrWriter(self.0, self.1) + } +} + +pub struct UserSlicePtrReader(*mut c_types::c_void, usize); + +impl UserSlicePtrReader { + /// Returns the number of bytes left to be read from this. Note that even + /// reading less than this number of bytes may return an Error(). + pub fn len(&self) -> usize { + self.1 + } + + /// Returns `true` if `self.len()` is 0. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Read all data remaining in the user slice and return it in a `Vec`. + /// + /// Returns EFAULT if the address does not currently point to + /// mapped, readable memory. + pub fn read_all(&mut self) -> error::KernelResult> { + let mut data = vec![0; self.1]; + self.read(&mut data)?; + Ok(data) + } + + pub fn read(&mut self, data: &mut [u8]) -> error::KernelResult<()> { + if data.len() > self.1 || data.len() > u32::MAX as usize { + return Err(error::Error::EFAULT); + } + let res = unsafe { + bindings::_copy_from_user( + data.as_mut_ptr() as *mut c_types::c_void, + self.0, + data.len() as _, + ) + }; + if res != 0 { + return Err(error::Error::EFAULT); + } + // Since this is not a pointer to a valid object in our program, + // we cannot use `add`, which has C-style rules for defined + // behavior. + self.0 = self.0.wrapping_add(data.len()); + self.1 -= data.len(); + Ok(()) + } +} + +pub struct UserSlicePtrWriter(*mut c_types::c_void, usize); + +impl UserSlicePtrWriter { + pub fn len(&self) -> usize { + self.1 + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn write(&mut self, data: &[u8]) -> error::KernelResult<()> { + if data.len() > self.1 || data.len() > u32::MAX as usize { + return Err(error::Error::EFAULT); + } + let res = unsafe { + bindings::_copy_to_user( + self.0, + data.as_ptr() as *const c_types::c_void, + data.len() as _, + ) + }; + if res != 0 { + return Err(error::Error::EFAULT); + } + // Since this is not a pointer to a valid object in our program, + // we cannot use `add`, which has C-style rules for defined + // behavior. + self.0 = self.0.wrapping_add(data.len()); + self.1 -= data.len(); + Ok(()) + } +} diff --git a/rust/shlex/Cargo.toml b/rust/shlex/Cargo.toml new file mode 100644 index 00000000000000..54e984fa2bf13a --- /dev/null +++ b/rust/shlex/Cargo.toml @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: MIT OR Apache-2.0 + +[package] +name = "shlex" +version = "0.1.1" +authors = ["comex "] +license = "MIT/Apache-2.0" +repository = "https://github.com/comex/rust-shlex" +description = """ +Split a string into shell words, like Python's shlex. +""" diff --git a/rust/shlex/src/lib.rs b/rust/shlex/src/lib.rs new file mode 100644 index 00000000000000..0b4a824fe4db33 --- /dev/null +++ b/rust/shlex/src/lib.rs @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +// Copyright 2015 Nicholas Allegra (comex). +// Licensed under the Apache License, Version 2.0 or +// the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +//! Same idea as (but implementation not directly based on) the Python shlex module. However, this +//! implementation does not support any of the Python module's customization because it makes +//! parsing slower and is fairly useless. You only get the default settings of shlex.split, which +//! mimic the POSIX shell: +//! http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html +//! +//! This implementation also deviates from the Python version in not treating \r specially, which I +//! believe is more compliant. +//! +//! The algorithms in this crate are oblivious to UTF-8 high bytes, so they iterate over the bytes +//! directly as a micro-optimization. + +use std::borrow::Cow; + +/// An iterator that takes an input string and splits it into the words using the same syntax as +/// the POSIX shell. +pub struct Shlex<'a> { + in_iter: std::str::Bytes<'a>, + /// The number of newlines read so far, plus one. + pub line_no: usize, + /// An input string is erroneous if it ends while inside a quotation or right after an + /// unescaped backslash. Since Iterator does not have a mechanism to return an error, if that + /// happens, Shlex just throws out the last token, ends the iteration, and sets 'had_error' to + /// true; best to check it after you're done iterating. + pub had_error: bool, +} + +impl<'a> Shlex<'a> { + pub fn new(in_str: &'a str) -> Self { + Shlex { + in_iter: in_str.bytes(), + line_no: 1, + had_error: false, + } + } + + fn parse_word(&mut self, mut ch: u8) -> Option { + let mut result: Vec = Vec::new(); + loop { + match ch as char { + '"' => if let Err(()) = self.parse_double(&mut result) { + self.had_error = true; + return None; + }, + '\'' => if let Err(()) = self.parse_single(&mut result) { + self.had_error = true; + return None; + }, + '\\' => if let Some(ch2) = self.next_char() { + if ch2 != '\n' as u8 { result.push(ch2); } + } else { + self.had_error = true; + return None; + }, + ' ' | '\t' | '\n' => { break; }, + _ => { result.push(ch as u8); }, + } + if let Some(ch2) = self.next_char() { ch = ch2; } else { break; } + } + unsafe { Some(String::from_utf8_unchecked(result)) } + } + + fn parse_double(&mut self, result: &mut Vec) -> Result<(), ()> { + loop { + if let Some(ch2) = self.next_char() { + match ch2 as char { + '\\' => { + if let Some(ch3) = self.next_char() { + match ch3 as char { + // \$ => $ + '$' | '`' | '"' | '\\' => { result.push(ch3); }, + // \ => nothing + '\n' => {}, + // \x => =x + _ => { result.push('\\' as u8); result.push(ch3); } + } + } else { + return Err(()); + } + }, + '"' => { return Ok(()); }, + _ => { result.push(ch2); }, + } + } else { + return Err(()); + } + } + } + + fn parse_single(&mut self, result: &mut Vec) -> Result<(), ()> { + loop { + if let Some(ch2) = self.next_char() { + match ch2 as char { + '\\' => { + if let Some(ch3) = self.next_char() { + match ch3 as char { + // for single quotes, only these can be escaped + '\'' | '\\' => { result.push(ch3); }, + _ => { result.push('\\' as u8); result.push(ch3); } + } + } else { + return Err(()); + } + }, + '\'' => { return Ok(()); }, + _ => { result.push(ch2); }, + } + } else { + return Err(()); + } + } + } + + fn next_char(&mut self) -> Option { + let res = self.in_iter.next(); + if res == Some('\n' as u8) { self.line_no += 1; } + res + } +} + +impl<'a> Iterator for Shlex<'a> { + type Item = String; + fn next(&mut self) -> Option { + if let Some(mut ch) = self.next_char() { + // skip initial whitespace + loop { + match ch as char { + ' ' | '\t' | '\n' => {}, + '#' => { + while let Some(ch2) = self.next_char() { + if ch2 as char == '\n' { break; } + } + }, + _ => { break; } + } + if let Some(ch2) = self.next_char() { ch = ch2; } else { return None; } + } + self.parse_word(ch) + } else { // no initial character + None + } + } + +} + +/// Convenience function that consumes the whole string at once. Returns None if the input was +/// erroneous. +pub fn split(in_str: &str) -> Option> { + let mut shl = Shlex::new(in_str); + let res = shl.by_ref().collect(); + if shl.had_error { None } else { Some(res) } +} + +/// Given a single word, return a string suitable to encode it as a shell argument. +pub fn quote(in_str: &str) -> Cow { + if in_str.len() == 0 { + "\"\"".into() + } else if in_str.bytes().any(|c| match c as char { + '|' | '&' | ';' | '<' | '>' | '(' | ')' | '$' | '`' | '\\' | '"' | '\'' | ' ' | '\t' | + '\r' | '\n' | '*' | '?' | '[' | '#' | '~' | '=' | '%' => true, + _ => false + }) { + let mut out: Vec = Vec::new(); + out.push('"' as u8); + for c in in_str.bytes() { + match c as char { + '$' | '`' | '"' | '\\' => out.push('\\' as u8), + _ => () + } + out.push(c); + } + out.push('"' as u8); + unsafe { String::from_utf8_unchecked(out) }.into() + } else { + in_str.into() + } +} + +#[cfg(test)] +static SPLIT_TEST_ITEMS: &'static [(&'static str, Option<&'static [&'static str]>)] = &[ + ("foo$baz", Some(&["foo$baz"])), + ("foo baz", Some(&["foo", "baz"])), + ("foo\"bar\"baz", Some(&["foobarbaz"])), + ("foo \"bar\"baz", Some(&["foo", "barbaz"])), + (" foo \nbar", Some(&["foo", "bar"])), + ("foo\\\nbar", Some(&["foobar"])), + ("\"foo\\\nbar\"", Some(&["foobar"])), + ("'baz\\$b'", Some(&["baz\\$b"])), + ("'baz\\\''", Some(&["baz\'"])), + ("\\", None), + ("\"\\", None), + ("'\\", None), + ("\"", None), + ("'", None), + ("foo #bar\nbaz", Some(&["foo", "baz"])), + ("foo #bar", Some(&["foo"])), + ("foo#bar", Some(&["foo#bar"])), + ("foo\"#bar", None), +]; + +#[test] +fn test_split() { + for &(input, output) in SPLIT_TEST_ITEMS { + assert_eq!(split(input), output.map(|o| o.iter().map(|&x| x.to_owned()).collect())); + } +} + +#[test] +fn test_lineno() { + let mut sh = Shlex::new("\nfoo\nbar"); + while let Some(word) = sh.next() { + if word == "bar" { + assert_eq!(sh.line_no, 3); + } + } +} + +#[test] +fn test_quote() { + assert_eq!(quote("foobar"), "foobar"); + assert_eq!(quote("foo bar"), "\"foo bar\""); + assert_eq!(quote("\""), "\"\\\"\""); + assert_eq!(quote(""), "\"\""); +} diff --git a/scripts/Makefile.build b/scripts/Makefile.build index a467b932344247..9b50f2e0202608 100644 --- a/scripts/Makefile.build +++ b/scripts/Makefile.build @@ -23,10 +23,14 @@ subdir-y := subdir-m := EXTRA_AFLAGS := EXTRA_CFLAGS := +EXTRA_RUSTCFLAGS := +EXTRA_CARGOFLAGS := EXTRA_CPPFLAGS := EXTRA_LDFLAGS := asflags-y := ccflags-y := +rustcflags-y := +cargoflags-y := cppflags-y := ldflags-y := @@ -299,6 +303,20 @@ quiet_cmd_cc_lst_c = MKLST $@ $(obj)/%.lst: $(src)/%.c FORCE $(call if_changed_dep,cc_lst_c) +# Compile Rust sources (.rs) via cargo +# --------------------------------------------------------------------------- + +quiet_cmd_cargo = CARGO $(quiet_modtag) $@ + cmd_cargo = cd $(src) && $(CARGO) build $(cargo_flags) + +# The .o from the Rust staticlib +$(obj)/%.o: $(src)/out/lib%.a + $(Q)$(LD) -r -o $@ --whole-archive $< + +# The Rust staticlib from cargo +$(obj)/out/lib%.a: FORCE + $(call cmd,cargo) + # Compile assembler sources (.S) # --------------------------------------------------------------------------- diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib index 3d599716940cb8..a74898ff131aee 100644 --- a/scripts/Makefile.lib +++ b/scripts/Makefile.lib @@ -20,6 +20,8 @@ endif # flags that take effect in current and sub directories KBUILD_AFLAGS += $(subdir-asflags-y) KBUILD_CFLAGS += $(subdir-ccflags-y) +KBUILD_RUSTCFLAGS += $(subdir-rustcflags-y) +KBUILD_CARGOFLAGS += $(subdir-cargoflags-y) # Figure out what we need to build from the various variables # =========================================================================== @@ -126,6 +128,14 @@ _c_flags = $(filter-out $(CFLAGS_REMOVE_$(target-stem).o), \ $(filter-out $(ccflags-remove-y), \ $(KBUILD_CPPFLAGS) $(KBUILD_CFLAGS) $(ccflags-y)) \ $(CFLAGS_$(target-stem).o)) +_rustc_flags = $(filter-out $(RUSTCFLAGS_REMOVE_$(target-stem).o), \ + $(filter-out $(rustcflags-remove-y), \ + $(KBUILD_RUSTCFLAGS) $(rustcflags-y)) \ + $(RUSTCFLAGS_$(target-stem).o)) +_cargo_flags = $(filter-out $(CARGOFLAGS_REMOVE_$(target-stem).o), \ + $(filter-out $(cargoflags-remove-y), \ + $(KBUILD_CARGOFLAGS) $(cargoflags-y)) \ + $(CARGOFLAGS_$(target-stem).o)) _a_flags = $(filter-out $(AFLAGS_REMOVE_$(target-stem).o), \ $(filter-out $(asflags-remove-y), \ $(KBUILD_CPPFLAGS) $(KBUILD_AFLAGS) $(asflags-y)) \ @@ -185,7 +195,8 @@ _cpp_flags += -I $(srctree)/$(src) -I $(objtree)/$(obj) endif endif -part-of-module = $(if $(filter $(basename $@).o, $(real-obj-m)),y) +# TODO: quick hack +part-of-module = $(if $(filter $(basename $@).o,$(real-obj-m)),y,$(and $(real-obj-m),$(shell echo $@ | sed -E 's!.+/out/lib.+\.a!y!;t;d'))) quiet_modtag = $(if $(part-of-module),[M], ) modkern_cflags = \ @@ -193,6 +204,16 @@ modkern_cflags = \ $(KBUILD_CFLAGS_MODULE) $(CFLAGS_MODULE), \ $(KBUILD_CFLAGS_KERNEL) $(CFLAGS_KERNEL) $(modfile_flags)) +modkern_rustcflags = \ + $(if $(part-of-module), \ + $(KBUILD_RUSTCFLAGS_MODULE) $(RUSTCFLAGS_MODULE), \ + $(KBUILD_RUSTCFLAGS_KERNEL) $(RUSTCFLAGS_KERNEL)) + +modkern_cargoflags = \ + $(if $(part-of-module), \ + $(KBUILD_CARGOFLAGS_MODULE) $(CARGOFLAGS_MODULE), \ + $(KBUILD_CARGOFLAGS_KERNEL) $(CARGOFLAGS_KERNEL)) + modkern_aflags = $(if $(part-of-module), \ $(KBUILD_AFLAGS_MODULE) $(AFLAGS_MODULE), \ $(KBUILD_AFLAGS_KERNEL) $(AFLAGS_KERNEL)) @@ -202,6 +223,22 @@ c_flags = -Wp,-MMD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) \ $(_c_flags) $(modkern_cflags) \ $(basename_flags) $(modname_flags) +# Needed for the Rust bindings +# TODO: we unconditionally pass `KBUILD_CFLAGS_MODULE` to avoid having +# dual bindings, but ideally we should generate two set of bindings +RUST_BINDGEN_CFLAGS = $(c_flags) $(KBUILD_CFLAGS_MODULE) +export RUST_BINDGEN_CFLAGS + +rustc_cfg_flags = $(shell sed -nE 's/^(CONFIG_[^=]+)=(y|m)$$/--cfg \1/p' $(srctree)/include/config/auto.conf | xargs) + +rustc_flags = $(_rustc_flags) $(modkern_rustcflags) $(rustc_cfg_flags) + +# Passed by cargo +RUSTFLAGS = $(rustc_flags) +export RUSTFLAGS + +cargo_flags = $(_cargo_flags) $(modkern_cargoflags) + a_flags = -Wp,-MMD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) \ $(_a_flags) $(modkern_aflags) diff --git a/scripts/kallsyms.c b/scripts/kallsyms.c index 0096cd96533275..a880a2c20b3d64 100644 --- a/scripts/kallsyms.c +++ b/scripts/kallsyms.c @@ -27,7 +27,7 @@ #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) -#define KSYM_NAME_LEN 128 +#define KSYM_NAME_LEN 256 struct sym_entry { unsigned long long addr; diff --git a/scripts/rust-version.sh b/scripts/rust-version.sh new file mode 100755 index 00000000000000..67b6d31688e24b --- /dev/null +++ b/scripts/rust-version.sh @@ -0,0 +1,31 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# +# rust-version rust-command +# +# Print the compiler version of `rust-command' in a 5 or 6-digit form +# such as `14502' for rustc-1.45.2 etc. +# +# Returns 0 if not found (so that Kconfig does not complain) +compiler="$*" + +if [ ${#compiler} -eq 0 ]; then + echo "Error: No compiler specified." >&2 + printf "Usage:\n\t$0 \n" >&2 + exit 1 +fi + +if ! command -v $compiler >/dev/null 2>&1; then + echo 0 + exit 0 +fi + +VERSION=$($compiler --version | cut -f2 -d' ') + +# Cut suffix if any (e.g. `-dev`) +VERSION=$(echo $VERSION | cut -f1 -d'-') + +MAJOR=$(echo $VERSION | cut -f1 -d'.') +MINOR=$(echo $VERSION | cut -f2 -d'.') +PATCHLEVEL=$(echo $VERSION | cut -f3 -d'.') +printf "%d%02d%02d\\n" $MAJOR $MINOR $PATCHLEVEL diff --git a/tools/include/linux/kallsyms.h b/tools/include/linux/kallsyms.h index efb6c3f5f2a9a5..5e9d717e339396 100644 --- a/tools/include/linux/kallsyms.h +++ b/tools/include/linux/kallsyms.h @@ -6,7 +6,7 @@ #include #include -#define KSYM_NAME_LEN 128 +#define KSYM_NAME_LEN 256 struct module; diff --git a/tools/include/linux/lockdep.h b/tools/include/linux/lockdep.h index e56997288f2b07..b497a16548a6cf 100644 --- a/tools/include/linux/lockdep.h +++ b/tools/include/linux/lockdep.h @@ -47,7 +47,7 @@ static inline int debug_locks_off(void) #define task_pid_nr(tsk) ((tsk)->pid) -#define KSYM_NAME_LEN 128 +#define KSYM_NAME_LEN 256 #define printk(...) dprintf(STDOUT_FILENO, __VA_ARGS__) #define pr_err(format, ...) fprintf (stderr, format, ## __VA_ARGS__) #define pr_warn pr_err