Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rust: generate bindings for helpers #359

Merged
merged 3 commits into from
Aug 26, 2021
Merged

Conversation

nbdd0121
Copy link
Member

@nbdd0121 nbdd0121 commented Jun 5, 2021

Helper functions are now only compiled if the wrapped function is not
made directly available via bindgen. For the compiled helper functions,
bindings for them are automatically generated via a second invocation
to bindgen, and the corresponding binding will have its rust_helper_
prefix removed, so Rust code can call bindings::foo regardless
whether foo is directly exposed or exposed via a helper.

Details about how this works:

  • bindings_generated.rs is first created via bindgen;
  • List of functions are extracted from bindings_generated.rs and a
    rust_helper_foo macro is generated for each function directly
    exposed this way;
  • helpers.c can then use C preprocessor to conditionally compile
    helpers only if they are not directly exposed.
  • bindgen invokes again to create bindings for helpers.c.
  • bindgen output is then postprocessed to remove the rust_helper
    prefix and add #[link_name = ""].

Related #352

@ksquirrel

This comment has been minimized.

@ksquirrel

This comment has been minimized.

@ksquirrel

This comment has been minimized.

@ksquirrel

This comment has been minimized.

@TheSven73
Copy link
Collaborator

I'm not sure I get what this does... could you provide a layperson description ? :)

If I understand correctly, we'd still have to create rust helpers manually in helpers.c, it's just that through some magic, you can now call them as if they were part of the bindings:: namespace. Is that correct?

@nbdd0121
Copy link
Member Author

nbdd0121 commented Jun 5, 2021

If I understand correctly, we'd still have to create rust helpers manually in helpers.c, it's just that through some magic, you can now call them as if they were part of the bindings:: namespace. Is that correct?

Yes, and if the header exposes functions as extern symbols instead of macro or static inline, that externed symbol will be used directly and the helper won't be compiled.

It's not magic :). Here's some detailed example. We know that errname is either an extern function or a static inline function depending some config.

If it is an extern function, then bindings_generated.rs will have something like:

extern "C" {
    fn errname(err: c_types::c_int) -> *const c_types::c_char;
}

which will cause bindings_generated.h to have this line:

#define rust_helper_errname

which means in helpers.c, the following lines are not compiled

#ifndef rust_helper_errname
const char *rust_helper_errname(int err)
{
	return errname(err);
}
EXPORT_SYMBOL_GPL(rust_helper_errname);
#endif

On the other hand, if it's defined as macro or static inline function, bindings_generated.rs contains nothing about errname, so the #define line is not present in bindings_generated.h. So the lines in helpers.c will be compiled, and the second bindgen invocation will produce something like:

extern "C" {
    fn rust_helper_errname(err: c_types::c_int) -> *const c_types::c_char;
}

which I use sed to replace into

extern "C" {
    #[link_name = "rust_helper_errname"]
    fn errname(err: c_types::c_int) -> *const c_types::c_char;
}

So the caller don't need to care about whether an extern symbol exists or a helper is needed; it just calls bindings::errname, and it will transparently be linked directly or to the helper.

@ksquirrel

This comment has been minimized.

@ksquirrel

This comment has been minimized.

@ksquirrel

This comment has been minimized.

@ksquirrel

This comment has been minimized.

@nbdd0121
Copy link
Member Author

nbdd0121 commented Jul 2, 2021

v1 -> v2

changed the mechanism used to prevent name conflict

  • previous C macros are used to conditional compile helpers, so a C header has to be generated
  • now helpers are generated regardless, and glob import is used to prevent name conflict
  • this means that some helpers will be dead code, but the logic is simpler and less magical

@ksquirrel
Copy link
Member

Review of 453bd738c601:

  • ✔️ Commit 453bd73: Looks fine!

@fbq
Copy link
Member

fbq commented Jul 6, 2021

I like the idea that boilerplate "extern"s can be removed ;-) But I think at least we want the helpers to have different namespaces with other bindings, consider the following case:

First we implement a helper named:

bool rust_helper_do_something()

and use it as

if unsafe { bindings::do_something() } {
    <xxx>
}

Then some one, who is not aware of our implementation and usage, also implement a do_something in kernel:

bool do_something() 
{
    ...
}

EXPORT_SYM(do_domething);

And it will be preferred by Rust code, so here comes the first problem, what if the C version of do_something works slightly differently? The difference may be not detectable by our existing tests yet still introduce bugs (e.g. break our SAFETY assumptions).

And there is another problem, if both Rust side code and C side code got evolved a bit around do_something, and suddenly C version of do_something got removed, and we switched back to rust_helper_do_something, we might have a hidden bug again because the difference of two functions may not be detectable to tests yet cause bugs.

Thoughts?

@bjorn3
Copy link
Member

bjorn3 commented Jul 6, 2021

Helpers are normally named after the macro they call. If a macro and a C function have the same name, that gives problems anyway as the macro shadows the function amd if you include the header with the macro before the header with the function, you get a syntax error or renamed function.

@nbdd0121
Copy link
Member Author

nbdd0121 commented Jul 6, 2021

I would consider it very problematic and confusing if a rust_helper_foo is functionally different from foo. rust_helper_foo should only be used to invoke macros or inline functions that are not otherwise exposed via bindgen.

It's a deliberate choice to use the same namespace, so that the Rust users do not have to care whether the function is directly exposed or exposed via a helper. Some functions like errname can be either a extern function or an static inline function depending on configs.

@fbq
Copy link
Member

fbq commented Jul 6, 2021

Helpers are normally named after the macro they call. If a macro and a C function have the same name, that gives problems anyway as the macro shadows the function amd if you include the header with the macro before the header with the function, you get a syntax error or renamed function.

Well, you said "normally", so there are exceptions, and the thing is, we don't have a way to detect the exception (at least I don't see one in this PR). Luckily the only exception seems to be rust_helper_refcount_new currently.

Having an undetectable problem is dangerous, and it's even more dangerous when the problem is normally rare.

@fbq
Copy link
Member

fbq commented Jul 6, 2021

I would consider it very problematic and confusing if a rust_helper_foo is functionally different from foo. rust_helper_foo should only be used to invoke macros or inline functions that are not otherwise exposed via bindgen.

As long as you can guarantee that rust_helper_foo always call foo directly, then I don't have any problem with this.

Now think about this, maybe we can try this if rust_helper_foo doesn't call foo directly: we just define foo in rust/helpers.c.

For example, instead of defining rust_helper_refcount_new, we just define refcount_new. This is kinda like taking the ownership of the name, if the refcount maintainers want to use that name later, they will find the conflicts and reach out to us, and we can just give it up and adjust if the semantics of refcount_new changes.

This means in rust/helpers, we either:

  • User a C macro to generate rust_helper_foo with calling foo as the function body, or
  • Define a foo to take the ownership of the name.

I think it's a simple and practical rule to follow when we add things in rust/helpers.c, and rules out some potential issues because of sharing namespace with normal bindings.

Thoughts?

It's a deliberate choice to use the same namespace, so that the Rust users do not have to care whether the function is directly exposed or exposed via a helper. Some functions like errname can be either a extern function or an static inline function depending on configs.

@nbdd0121
Copy link
Member Author

This means in rust/helpers, we either:

  • User a C macro to generate rust_helper_foo with calling foo as the function body, or
  • Define a foo to take the ownership of the name.

I think it's a simple and practical rule to follow when we add things in rust/helpers.c, and rules out some potential issues because of sharing namespace with normal bindings.

Thoughts?

Sounds very reasonable to me.

@nbdd0121
Copy link
Member Author

Rebased, and changed rust_helper_refcount_new to rust_helper_REFCOUNT_INIT to address @fbq's concern for now.

@nbdd0121 nbdd0121 requested review from ojeda and fbq July 30, 2021 16:26
@ojeda
Copy link
Member

ojeda commented Aug 24, 2021

Solving the conflicts and trying this one, we get a warning about a clashing BUG() due to the panic handler being now in the kernel crate.

bindgen does not seem to understand _Noreturn yet, opened: rust-lang/rust-bindgen#2094.

Automatically generate rust bindings for helper functions via bindgen.
The corresponding Rust bindings will have their `rust_helper_`
prefix removed, so Rust code can call `bindings::foo` regardless
whether `foo` is directly exposed or exposed via a helper.

When both a directly exposed symbol and a helper exists, the directly
exposed symbol will take precedence.

Signed-off-by: Gary Guo <[email protected]>
@ojeda
Copy link
Member

ojeda commented Aug 24, 2021

Perhaps we should have "manual helpers" for bits like this, something like:

diff --git a/rust/helpers.c b/rust/helpers.c
index b74c8991fb9..a6ef4cd02e0 100644
--- a/rust/helpers.c
+++ b/rust/helpers.c
@@ -13,7 +13,9 @@
 #include <linux/security.h>
 #include <asm/io.h>
 
-void rust_helper_BUG(void)
+// Manual until `bindgen` supports `__noreturn`
+// https://github.com/rust-lang/rust-bindgen/issues/2094
+__noreturn void rust_manual_helper_BUG(void)
 {
        BUG();
 }
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index c6bf9043afb..a95c4f57e8c 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -233,9 +233,9 @@ macro_rules! container_of {
 #[panic_handler]
 fn panic(info: &core::panic::PanicInfo<'_>) -> ! {
     extern "C" {
-        fn rust_helper_BUG() -> !;
+        fn rust_manual_helper_BUG() -> !;
     }
     pr_emerg!("{}\n", info);
     // SAFETY: FFI call.
-    unsafe { rust_helper_BUG() };
+    unsafe { rust_manual_helper_BUG() };
 }

@nbdd0121
Copy link
Member Author

Rebased and fixed conflicts.

I think given that there aren't many noreturn ffi functions, we can just live with it for now. I've added a loop {} underneath bindings::BUG() call.

@ojeda
Copy link
Member

ojeda commented Aug 24, 2021

I think given that there aren't many noreturn ffi functions, we can just live with it for now. I've added a loop {} underneath bindings::BUG() call.

I think we crossed messages -- see my previous one.

Please add the __noreturn on the helper and the comment on top. Regarding the loop {} or the manual helper, I think both approaches are fine. The manual helper may generate one less instruction, but...

@nbdd0121
Copy link
Member Author

Ok. I keep the loop{} approach and thus add the comment to the call site instead.

One single additional instruction on the perhaps coldest code path.. I am not worried. It could be removed with unsafe { unreachable_unchecked() } but I think the gain doesn't warrant an additional unsafe...

@nbdd0121
Copy link
Member Author

I think the change exposes a bug in IoMem. readq isn't gated on CONFIG_64BIT.

@ojeda ojeda merged commit b00d4c3 into Rust-for-Linux:rust Aug 26, 2021
@nbdd0121 nbdd0121 deleted the helper branch September 6, 2021 18:35
metaspace pushed a commit to metaspace/linux that referenced this pull request Apr 16, 2024
Similar to 2c9f029 ("netfilter: nf_tables: flush pending destroy
work before netlink notifier") to address a race between exit_net and
the destroy workqueue.

The trace below shows an element to be released via destroy workqueue
while exit_net path (triggered via module removal) has already released
the set that is used in such transaction.

[ 1360.547789] BUG: KASAN: slab-use-after-free in nf_tables_trans_destroy_work+0x3f5/0x590 [nf_tables]
[ 1360.547861] Read of size 8 at addr ffff888140500cc0 by task kworker/4:1/152465
[ 1360.547870] CPU: 4 PID: 152465 Comm: kworker/4:1 Not tainted 6.8.0+ Rust-for-Linux#359
[ 1360.547882] Workqueue: events nf_tables_trans_destroy_work [nf_tables]
[ 1360.547984] Call Trace:
[ 1360.547991]  <TASK>
[ 1360.547998]  dump_stack_lvl+0x53/0x70
[ 1360.548014]  print_report+0xc4/0x610
[ 1360.548026]  ? __virt_addr_valid+0xba/0x160
[ 1360.548040]  ? __pfx__raw_spin_lock_irqsave+0x10/0x10
[ 1360.548054]  ? nf_tables_trans_destroy_work+0x3f5/0x590 [nf_tables]
[ 1360.548176]  kasan_report+0xae/0xe0
[ 1360.548189]  ? nf_tables_trans_destroy_work+0x3f5/0x590 [nf_tables]
[ 1360.548312]  nf_tables_trans_destroy_work+0x3f5/0x590 [nf_tables]
[ 1360.548447]  ? __pfx_nf_tables_trans_destroy_work+0x10/0x10 [nf_tables]
[ 1360.548577]  ? _raw_spin_unlock_irq+0x18/0x30
[ 1360.548591]  process_one_work+0x2f1/0x670
[ 1360.548610]  worker_thread+0x4d3/0x760
[ 1360.548627]  ? __pfx_worker_thread+0x10/0x10
[ 1360.548640]  kthread+0x16b/0x1b0
[ 1360.548653]  ? __pfx_kthread+0x10/0x10
[ 1360.548665]  ret_from_fork+0x2f/0x50
[ 1360.548679]  ? __pfx_kthread+0x10/0x10
[ 1360.548690]  ret_from_fork_asm+0x1a/0x30
[ 1360.548707]  </TASK>

[ 1360.548719] Allocated by task 192061:
[ 1360.548726]  kasan_save_stack+0x20/0x40
[ 1360.548739]  kasan_save_track+0x14/0x30
[ 1360.548750]  __kasan_kmalloc+0x8f/0xa0
[ 1360.548760]  __kmalloc_node+0x1f1/0x450
[ 1360.548771]  nf_tables_newset+0x10c7/0x1b50 [nf_tables]
[ 1360.548883]  nfnetlink_rcv_batch+0xbc4/0xdc0 [nfnetlink]
[ 1360.548909]  nfnetlink_rcv+0x1a8/0x1e0 [nfnetlink]
[ 1360.548927]  netlink_unicast+0x367/0x4f0
[ 1360.548935]  netlink_sendmsg+0x34b/0x610
[ 1360.548944]  ____sys_sendmsg+0x4d4/0x510
[ 1360.548953]  ___sys_sendmsg+0xc9/0x120
[ 1360.548961]  __sys_sendmsg+0xbe/0x140
[ 1360.548971]  do_syscall_64+0x55/0x120
[ 1360.548982]  entry_SYSCALL_64_after_hwframe+0x55/0x5d

[ 1360.548994] Freed by task 192222:
[ 1360.548999]  kasan_save_stack+0x20/0x40
[ 1360.549009]  kasan_save_track+0x14/0x30
[ 1360.549019]  kasan_save_free_info+0x3b/0x60
[ 1360.549028]  poison_slab_object+0x100/0x180
[ 1360.549036]  __kasan_slab_free+0x14/0x30
[ 1360.549042]  kfree+0xb6/0x260
[ 1360.549049]  __nft_release_table+0x473/0x6a0 [nf_tables]
[ 1360.549131]  nf_tables_exit_net+0x170/0x240 [nf_tables]
[ 1360.549221]  ops_exit_list+0x50/0xa0
[ 1360.549229]  free_exit_list+0x101/0x140
[ 1360.549236]  unregister_pernet_operations+0x107/0x160
[ 1360.549245]  unregister_pernet_subsys+0x1c/0x30
[ 1360.549254]  nf_tables_module_exit+0x43/0x80 [nf_tables]
[ 1360.549345]  __do_sys_delete_module+0x253/0x370
[ 1360.549352]  do_syscall_64+0x55/0x120
[ 1360.549360]  entry_SYSCALL_64_after_hwframe+0x55/0x5d

(gdb) list *__nft_release_table+0x473
0x1e033 is in __nft_release_table (net/netfilter/nf_tables_api.c:11354).
11349           list_for_each_entry_safe(flowtable, nf, &table->flowtables, list) {
11350                   list_del(&flowtable->list);
11351                   nft_use_dec(&table->use);
11352                   nf_tables_flowtable_destroy(flowtable);
11353           }
11354           list_for_each_entry_safe(set, ns, &table->sets, list) {
11355                   list_del(&set->list);
11356                   nft_use_dec(&table->use);
11357                   if (set->flags & (NFT_SET_MAP | NFT_SET_OBJECT))
11358                           nft_map_deactivate(&ctx, set);
(gdb)

[ 1360.549372] Last potentially related work creation:
[ 1360.549376]  kasan_save_stack+0x20/0x40
[ 1360.549384]  __kasan_record_aux_stack+0x9b/0xb0
[ 1360.549392]  __queue_work+0x3fb/0x780
[ 1360.549399]  queue_work_on+0x4f/0x60
[ 1360.549407]  nft_rhash_remove+0x33b/0x340 [nf_tables]
[ 1360.549516]  nf_tables_commit+0x1c6a/0x2620 [nf_tables]
[ 1360.549625]  nfnetlink_rcv_batch+0x728/0xdc0 [nfnetlink]
[ 1360.549647]  nfnetlink_rcv+0x1a8/0x1e0 [nfnetlink]
[ 1360.549671]  netlink_unicast+0x367/0x4f0
[ 1360.549680]  netlink_sendmsg+0x34b/0x610
[ 1360.549690]  ____sys_sendmsg+0x4d4/0x510
[ 1360.549697]  ___sys_sendmsg+0xc9/0x120
[ 1360.549706]  __sys_sendmsg+0xbe/0x140
[ 1360.549715]  do_syscall_64+0x55/0x120
[ 1360.549725]  entry_SYSCALL_64_after_hwframe+0x55/0x5d

Fixes: 0935d55 ("netfilter: nf_tables: asynchronous release")
Signed-off-by: Pablo Neira Ayuso <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

6 participants