Lints
PL/Rust has its own "rustc driver" named plrustc
. This must be installed using the
plrustc/build.sh
script and the resulting executable must be on the PATH
, or it should reside
somewhere that is included in the plrust.PATH_override
GUC.
PL/Rust uses its own "rustc driver" so that it can employ custom lints to detect certain Rust code idioms and patterns that trigger "I-Unsound" bugs in Rust itself. Think "clippy" but built into the Rust compiler itself. In addition to these custom lints, PL/Rust uses some standard Rust lints to enforce safety.
The plrust.required_lints
GUC defines which lints must have been applied to a function before PL/Rust will load the
library and execute the function. The default value is the empty set -- PL/Rust will not require any specific lints to
have been previously applied to a function.
Using the PLRUST_REQUIRED_LINTS
environment variable, it is possible to enforce that certain lints are always required
of compiled functions, regardless of the plrust.required_lints
GUC value.PLRUST_REQUIRED_LINTS
's format is a
comma-separated list of lint named. It must be set in the environment in which Postgres is started. The intention here
is that the system administrator can force certain lints for execution if for some reason postgresql.conf
or the users
able to modify it are not trusted.
In all cases, these lints are added to the generated code which wraps the user's LANGUAGE plrust
function, as
#![forbid(${lint_name})]
. They are used with "forbid" to ensure a user function cannot change it back to "allow".
PL/Rust does not apply these lints to dependant, external crates. Dependencies are allowed to internally use
whatever code they want, including unsafe
. Note that any public-facing unsafe
functions won't be callable by a plrust
function.
Dependencies are granted more freedom as the usable set can be controlled via the plrust.allowed_dependencies
GUC.
It is the administrator's responsibility to properly vet external dependencies for safety issues that may impact the running environment.
Any LANGUAGE plrust
code that triggers any of the below lints will fail to compile, indicating the triggered lint.
Standard Rust Lints
unknown_lints
https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#unknown-lints
PL/Rust won't allow any unknown (to our "rustc driver") lints to be applied. The justification for this is to mainly
guard against type-os in the plrust.compile_lints
GUC.
unsafe_code
https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html#unsafe-code
PL/Rust does not allow usage of unsafe
code in LANGUAGE plrust
functions. This includes all the unsafe idioms such
as dereferencing pointers and calling other unsafe
functions.
implied_bounds_entailment
https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#implied-bounds-entailment
This lint detects cases where the arguments of an impl method have stronger implied bounds than those from the trait method it's implementing.
If used incorrectly, this can be used to implement unsound APIs.
deprecated
https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#deprecated
The deprecated lint detects use of deprecated items. This is forbidden because certain items in the Rust standard library are incorrectly-safe APIs but were only deprecated rather than removed when a version with the appropriate safety annotation was added.
suspicious_auto_trait_impls
https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#suspicious-auto-trait-impls
This defends against some patterns that can lead to soundness issues. These cases currently can only trigger in patterns which are otherwise blocked by the unsafe_code
lint, but for better defense-in-depth, it's explicitly forbidden in PL/Rust.
soft_unstable
https://doc.rust-lang.org/rustc/lints/listing/deny-by-default.html#soft-unstable
This prevents the use of language and library features which were accidentally stabilized. This is forbidden because there's no reason to need to use these, and forbidding them reduces the set of APIs and features we have to consider in PL/Rust.
where_clauses_object_safety
https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#where-clauses-object-safety
This avoids some soundness holes that are in the language which can be used to trigger various crashes, see the lint documentation for details.
PL/Rust plrustc
Lints
plrust_extern_blocks
This blocks the declaration of extern "API" {}"
blocks. Primarily, this is to ensure a plrust function cannot
declare internal Postgres symbols as external.
For example, this code pattern is blocked:
#![allow(unused)] fn main() { extern "C" { pub fn palloc(size: Size) -> *mut ::std::os::raw::c_void; } }
plrust_lifetime_parameterized_traits
Traits parameterized by lifetimes can be used to exploit Rust compiler bugs that lead to unsoundness issues. PL/Rust does not allow such traits to be declared.
For example, this code pattern is blocked:
#![allow(unused)] fn main() { trait Foo<'a> {} }
plrust_filesystem_macros
Filesystem macros such as include_bytes!
and include_str!
are disallowed, as they provide access to the underlying filesystem which should be unavailable to a trusted language handler.
For example, this code pattern is blocked:
#![allow(unused)] fn main() { const SOMETHING: &str = include_str!("/etc/passwd"); }
plrust_fn_pointers
Currently, several soundness holes have to do with the interaction between function pointers, implied bounds, and nested references. As a stopgap against these, use of function pointer types and function trait objects are currently blocked. This lint will likely be made more precise in the future.
Note that function types (such as the types resulting from closures as required by iterator functions) are still allowed, as these do not have the issues around variance.
For example, the following code pattern is blocked:
#![allow(unused)] fn main() { fn takes_fn_arg(x: fn()) { x(); } }
plrust_async
Currently async/await are forbidden by PL/Rust due to unclear interactions around lifetime and soundness constraints. This may be out of an overabundance of caution. Specifically, code like the following will fail to compile:
#![allow(unused)] fn main() { async fn an_async_fn() { // ... } fn normal_function() { let async_block = async { // ... }; // ... } }
plrust_leaky
This lint forbids use of "leaky" functions such as mem::forget
and Box::leak
. While leaking memory is considered safe, it has undesirable effects and thus is blocked by default. For example, the lint will trigger on (at least) the following code:
#![allow(unused)] fn main() { core::mem::forget(something); let foo = Box::leak(Box::new(1u32)); let bar = vec![1, 2, 3].leak(); }
Note that this will not prevent all leaks, as PL/Rust code could still create a leak by constructing a reference cycle using Rc/Arc, for example.
plrust_env_macros
This lint forbids use of environment macros such as env!
and option_env!
, as it allows access to data that should not be available to a trusted language handler.
#![allow(unused)] fn main() { let path = env!("PATH"); let rustup_toolchain_dir = option_env!("RUSTUP_TOOLCHAIN"); // ... }
plrust_external_mod
This lint forbids use of non-inline mod blah
, as it can be used to access files a trusted language handler should not give access to.
#![allow(unused)] fn main() { // This is allowed mod foo { // some functions or whatever here... } // This is disallowed. mod bar; // More importantly, this is disallowed as well. #[path = "/sneaky/path/to/something"] mod baz; }
plrust_print_macros
This lint forbids use of the println!
/eprintln!
family of macros (including dbg!
and the non-ln
variants), as these allow bypassing the norm. Users should use pgrx::log!
or pgrx::debug!
instead.
#![allow(unused)] fn main() { println!("hello"); print!("plrust"); eprintln!("this is also blocked"); eprint!("even without the newline"); dbg!("same here"); }
plrust_stdio
This lint forbids use of the functions for accessing standard streams (stdin, stdout, stderr) from PL/Rust, for the same reason as above. For example, the following code is forbidden:
#![allow(unused)] fn main() { std::io::stdout().write_all(b"foobar").unwrap(); std::io::stderr().write_all(b"foobar").unwrap(); let _stdin_is_forbidden_too = std::io::stdin(); }
plrust_static_impls
This lint forbids certain impl
blocks for types containing &'static
references. The precise details are somewhat obscure, but can usually be avoided by making a custom struct to contain your static reference, which avoids the particular soundness hole we're concerned with. For example:
#![allow(unused)] fn main() { // This is forbidden: impl SomeTrait for (&'static Foo, Bar) { // ... } // Instead, do this: struct MyType(&'static Foo, Bar); impl SomeTrait for MyType { // ... } }
plrust_autotrait_impls
This lint forbids explicit implementations of the safe auto traits, as a workaround for various soundness holes around these. It may be relaxed in the future if those are fixed.
#![allow(unused)] fn main() { struct Foo(std::cell::Cell<i32>, std::marker::PhantomPinned); // Any of the following implementations would be forbidden. impl std::panic::UnwindSafe for Foo {} impl std::panic::RefUnwindSafe for Foo {} impl std::marker::Unpin for Foo {} }
As a workaround, in most cases, you should be able to use std::panic::AssertUnwindSafe
instead of implementing one of the UnwindSafe
traits, and Boxing your type can usually work around the need for Unpin
(which should be rare in non-async
code anyway).
plrust_suspicious_trait_object
This lint forbids trait object use in turbofish and generic defaults. This is an effort to fix certain soundness holes in the Rust language. More simply, the following patterns are disallowed:
// Trait object in turbofish
foo::<dyn SomeTrait>();
// Trait object in type default (enum, union, trait, and so on are all also forbidden)
struct SomeStruct<T = dyn SomeTrait>(...);
plrust_closure_trait_impl
This lint forbids trait impls over generic over Fn
, FnOnce
, FnMut
or
FnPtr
types. This is to work around some soundness issues where closure types
are incorrectly 'static
. For example, the following is forbidden:
trait Trait {}
// This is generic over a function trait.
impl<F: Fn()> Trait for F {}
However, this is currently overly strict. In the future, it may be relaxed to forbid only the case where the return type is projected into an associated item on the trait, as in:
trait Trait {
type Assoc;
}
impl<R, F: Fn() -> R> Trait for F {
// This is the problem
type Assoc = R;
}