Skip to content

Commit 4451786

Browse files
committed
fix_nvoc_pfunc*.cocci: fix bad pFunc fprt casts
Signed-off-by: Mathias Krause <minipli@grsecurity.net>
1 parent dcb4a9c commit 4451786

File tree

6 files changed

+285
-0
lines changed

6 files changed

+285
-0
lines changed

src/nvidia/cocci.sh

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,49 @@
77
#
88
# (c) 2025,2026 Open Source Security, Inc. All Rights Reserved.
99

10+
pfunc_filter() {
11+
# What a hack!
12+
#
13+
# coccinelle doesn't evaluate both sides of a #if ... #else ... #endif
14+
# block and even worse, it doesn't even implement enough preprocessor
15+
# logic to actually understand the #if expression and always only
16+
# handles the #if branch for non-trivial expressions.
17+
# Work around that by modifying the expression to '#if 0' and use
18+
# --noif0-passing to get the #else branch. *sigh!*
19+
#
20+
# Hack #2:
21+
# On top of that we need yet another hack to get thunk definitions as
22+
# blindly adding thunks for all ever possible functions doesn't work
23+
# (there are multiple decls that'd lead to multiple static inline thunk
24+
# definitions). So we record which functions we want a thunk for via
25+
# pfunc_list_filter() and use that to actually generate thunks only for
26+
# these. Gross!
27+
case "$1" in
28+
pre) sed 's|\(#if\) \(NVOC_EXPORTED_METHOD_DISABLED_BY_FLAG\)|\1 0//\2|' -i generated/*.[ch]; ;;
29+
diff) sed 's|\(#if\) 0//\(NVOC_EXPORTED_METHOD_DISABLED_BY_FLAG\)|\1 \2|'; ;;
30+
post) sed 's|\(#if\) 0//\(NVOC_EXPORTED_METHOD_DISABLED_BY_FLAG\)|\1 \2|' -i generated/*.[ch]; ;;
31+
esac
32+
}
33+
34+
pfunc_list_filter() {
35+
case "$1" in
36+
diff)
37+
fifo=$(mktemp -u)
38+
mkfifo "$fifo"
39+
40+
sed -n 's|^-.*pFunc=.*&\(.*\),|\1|p' <"$fifo" >pfunc.list &
41+
sed_pid=$!
42+
43+
pfunc_filter "$@" | tee "$fifo"
44+
45+
wait $sed_pid
46+
rm "$fifo"
47+
;;
48+
49+
*) pfunc_filter "$@";;
50+
esac
51+
}
52+
1053
null_filter() {
1154
case "$1" in
1255
pre) ;;
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Coccinelle script to fix 'pFunc' function pointer casts to use proper
2+
// types to prevent runtime CFI violations, e.g. under RAP.
3+
//
4+
// As for fix_nvoc_dtor.cocci, we need to create a thunk. The thunk takes two
5+
// arguments, even if the implementation takes only one. That'll get handled
6+
// when adding a thunk definition.
7+
//
8+
// NV_STATUS (*pFunc)(void *, void *);
9+
//
10+
// As coccinelle cannot handle the preprocessor branches for the 'pFunc'
11+
// assignment correctly, we need a workaround that simplifies the code so
12+
// coccinelle can understand it (see cocci.sh:pfunc_filter()).
13+
//
14+
// This script only handles the part of replacing taking the address of the
15+
// real function. See fix_nvoc_pfunc_null.cocci for the NULL fptr cast mangling
16+
// and fix_nvoc_pfunc_thunk.cocci for the thunk generation.
17+
//
18+
// (c) 2025,2026 Open Source Security, Inc. All Rights Reserved.
19+
20+
@initialize:python@
21+
@@
22+
import re
23+
24+
# cocci's regex support is too limiting, use python for the filtering
25+
type_match = re.compile(r"void *\( *\* *\) *\( *void *\)") # cocci adds spaces
26+
func_match = re.compile(r"_(IMPL|DISPATCH|KERNEL|[0-9a-f]{6})$")
27+
28+
// search for (function) pointer casts (filtered in @pfunc_cast_filter@)
29+
@pfunc_cast disable drop_cast@
30+
identifier fn;
31+
type T;
32+
@@
33+
(T) &fn
34+
35+
@script:python pfunc_cast_filter@
36+
t << pfunc_cast.T;
37+
f << pfunc_cast.fn;
38+
@@
39+
if not type_match.search(t) or not func_match.search(f):
40+
# print(f">>> '({t}) {f}' didn't match")
41+
cocci.include_match(False)
42+
43+
// drop cast and replace function with a thunk
44+
@remove_cast depends on pfunc_cast_filter@
45+
identifier pfunc_cast.fn;
46+
type pfunc_cast.T;
47+
fresh identifier fnthunk = "THUNK_" ## fn;
48+
@@
49+
- (T) &fn
50+
+ &fnthunk
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Coccinelle script to fix 'pFunc' function pointer casts to use proper types.
2+
//
3+
// Similar to fix_nvoc_pfunc_addr.cocci, just for the NULL fptr.
4+
//
5+
// We simply want to drop the cast as it's unneeded. As it's in the '#if 0'
6+
// block, we need to tell cocci to look at that:
7+
//
8+
// options: --noif0-passing
9+
//
10+
// (c) 2025,2026 Open Source Security, Inc. All Rights Reserved.
11+
12+
@initialize:python@
13+
@@
14+
import re
15+
16+
type_match = re.compile(r"void *\( *\* *\) *\( *void *\)") # cocci adds spaces
17+
18+
// search for (function) pointer casts (filtered in @pfunc_cast_filter@)
19+
@pfunc_null_cast disable drop_cast@
20+
type T;
21+
@@
22+
(T) NULL
23+
24+
@script:python pfunc_cast_filter@
25+
t << pfunc_null_cast.T;
26+
@@
27+
if not type_match.search(t):
28+
cocci.include_match(False)
29+
30+
@remove_null_cast depends on pfunc_cast_filter@
31+
type pfunc_null_cast.T;
32+
@@
33+
- (T)
34+
NULL
35+
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Coccinelle script to fix 'pFunc' function pointer casts to use proper
2+
// types to prevent runtime CFI violations, e.g. under RAP.
3+
//
4+
// fix_nvoc_pfunc_addr.cocci already replaced the function pointer with a
5+
// thunk. What's left is to actually generate the thunk declarations and
6+
// definitions.
7+
//
8+
// NV_STATUS (*pFunc)(void *, void *);
9+
//
10+
// This script depends on fix_nvoc_pfunc_addr.cocci to generate a file
11+
// 'pfunc.list' with all the functions that have been replaced (see
12+
// cocci.sh:pfunc_list_filter()).
13+
//
14+
// (c) 2025,2026 Open Source Security, Inc. All Rights Reserved.
15+
16+
@initialize:python@
17+
@@
18+
with open('pfunc.list', 'r') as file:
19+
pfuncs = file.read().splitlines()
20+
21+
// search for pfunc decls, filtered by @pfunc_decl_filter@
22+
@pfunc_decl@
23+
identifier fn;
24+
identifier a1, a2;
25+
type T1, T2, R;
26+
@@
27+
(
28+
R fn(T1 a1);
29+
|
30+
R fn(T1 a1, T2 a2);
31+
)
32+
33+
@script:python pfunc_decl_filter@
34+
func << pfunc_decl.fn;
35+
@@
36+
if func not in pfuncs:
37+
cocci.include_match(False)
38+
#else:
39+
# print(f">>> {func} included")
40+
41+
// Add thunk decls right next to the original function's decl to make sure
42+
// it'll be declared when its address is taken.
43+
//
44+
// XXX: merging @pfunc_thunk_decl_two_args@ and @pfunc_thunk_decl_one_arg@
45+
// XXX: doesn't work :(
46+
@pfunc_thunk_decl_two_args depends on pfunc_decl_filter@
47+
identifier pfunc_decl.fn;
48+
identifier pfunc_decl.a1, pfunc_decl.a2;
49+
type pfunc_decl.T1, pfunc_decl.T2, pfunc_decl.R;
50+
fresh identifier thunk = "THUNK_" ## fn;
51+
typedef NV_STATUS;
52+
@@
53+
// XXX: matching function decls is only poorly supported, so we need this hack
54+
-R fn(T1 a1, T2 a2)
55+
+R fn(T1 a1, T2 a2);
56+
+NV_STATUS thunk(void *obj, void *arg)
57+
;
58+
59+
// TODO: merge with @pfunc_thunk_decl_two_args@
60+
@pfunc_thunk_decl_one_arg depends on pfunc_decl_filter@
61+
identifier pfunc_decl.fn;
62+
identifier pfunc_decl.a1;
63+
type pfunc_decl.T1, pfunc_decl.R;
64+
fresh identifier thunk = "THUNK_" ## fn;
65+
typedef NV_STATUS;
66+
@@
67+
// XXX: matching function decls is only poorly supported, so we need this hack
68+
-R fn(T1 a1)
69+
+R fn(T1 a1);
70+
+NV_STATUS thunk(void *obj, void *arg)
71+
;
72+
73+
// XXX: TODO: thunk definitions -> these need '--dir src/' override!
74+
@pfunc_thunk_def_two_args depends on pfunc_decl_filter@
75+
identifier pfunc_decl.fn;
76+
identifier pfunc_decl.a1, pfunc_decl.a2;
77+
type pfunc_decl.T1, pfunc_decl.T2, pfunc_decl.R;
78+
fresh identifier thunk = "THUNK_" ## fn;
79+
typedef NV_STATUS;
80+
@@
81+
// XXX: matching function decls is only poorly supported, so we need this hack
82+
R fn(T1 a1, T2 a2) { ... }
83+
+NV_STATUS thunk(void *obj, void *arg)
84+
+{
85+
+ return fn((T1)obj, (T2)arg);
86+
+}
87+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Coccinelle script to fix 'pFunc' member function pointer type to match what
2+
// fix_nvoc_pfunc.cocci expects.
3+
//
4+
// The structure type is located in inc/libraries/nvoc/runtime.h, override
5+
// the --dir ... parameter to only patch that:
6+
//
7+
// options: --dir inc/libraries/nvoc/
8+
//
9+
// (c) 2025,2026 Open Source Security, Inc. All Rights Reserved.
10+
11+
@pfunc_member@
12+
typedef NV_STATUS;
13+
@@
14+
struct NVOC_EXPORTED_METHOD_DEF
15+
{
16+
...
17+
- void (*pFunc) (...)
18+
+ NV_STATUS (*pFunc)(void *, void *)
19+
;
20+
...
21+
};
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Coccinelle script to fix 'pFunc' function pointer users to use the type
2+
// expected by fix_nvoc_pfunc_type.cocci and ensure it gets passed two args.
3+
//
4+
// NV_STATUS (*pFunc)(void *, void *);
5+
//
6+
// pFunc use is in src/kernel/gpu/deferred_api.c, src/kernel/gpu/gpu.c and
7+
// src/libraries/resserv/src/rs_resource.c, therefore override the --dir
8+
// option:
9+
//
10+
// options: --dir src/
11+
//
12+
// (c) 2025,2026 Open Source Security, Inc. All Rights Reserved.
13+
14+
// cases with a local variable
15+
@pfunc_use_var@
16+
struct NVOC_EXPORTED_METHOD_DEF *e;
17+
typedef NV_STATUS;
18+
expression arg;
19+
identifier fn;
20+
type T;
21+
@@
22+
23+
-T fn = ((T) e->pFunc)
24+
+NV_STATUS (*fn)(void *, void *) = e->pFunc
25+
;
26+
<...
27+
fn(
28+
arg
29+
+ , NULL
30+
)
31+
...>
32+
33+
// cases which cast and use ->pFunc directly
34+
@pfunc_use_cast@
35+
struct NVOC_EXPORTED_METHOD_DEF *e;
36+
expression arg1, arg2;
37+
type T;
38+
@@
39+
(
40+
-((T) e->pFunc)
41+
+(pEntry->pFunc)
42+
(arg1, arg2)
43+
|
44+
-((T) e->pFunc)
45+
+(pEntry->pFunc)
46+
(arg1
47+
+ , NULL
48+
)
49+
)

0 commit comments

Comments
 (0)