Skip to content

Commit e12b694

Browse files
committed
kernel: sucompat: increase reliability, commonize and micro-optimize (tiann#2656)
On plain ARMv8.0 devices (A53,A57,A73), strncpy_from_user_nofault() sometimes fails to copy `filename_user` string correctly. This breaks su ofc, breaking some apps like Termux (Play Store ver), ZArchiver and Root Explorer. This does NOT seem to affect newer ARMv8.2+ CPUs (A75/A76 and newer) My speculation? ARMv8.0 has weak speculation :) here we replace `ksu_strncpy_from_user_nofault` with ksu_strncpy_from_user_retry: - ksu_strncpy_from_user_nofault as fast-path copy - fallback to access_ok to validate the pointer + strncpy_from_user - manual null-termination just in case, as strncpy_from_user_nofault also does it - remove that memset, seems useless as it is an strncpy, not strncat basically, we retry on pagefualt for usercopies, its not like were doing memset(dest, 0, sizeof(dest)); strncat(dest, var, bytes); that memset seems unneeded. instead we use strncpy itself to do proper error and oob check and null term it after. as for optimizations - just return early if unauthorized - commonized logic - reduced duplication Tested on: - ARMv8.0 A73.a53, A57.a53, A53.a53 - ARMv8.2 A76.a55 Stale: tiann#2656 Signed-off-by: backslashxx <[email protected]>
1 parent f53d780 commit e12b694

File tree

3 files changed

+80
-56
lines changed

3 files changed

+80
-56
lines changed

kernel/kernel_compat.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,12 @@ long ksu_strncpy_from_user_nofault(char *dst, const void __user *unsafe_addr,
173173
return ret;
174174
}
175175
#endif
176+
177+
int ksu_access_ok(const void *addr, unsigned long size)
178+
{
179+
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,0,0)
180+
return access_ok(addr, size);
181+
#else
182+
return access_ok(VERIFY_READ, addr, size);
183+
#endif
184+
}

kernel/kernel_compat.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,6 @@ extern ssize_t ksu_kernel_read_compat(struct file *p, void *buf, size_t count,
2222
extern ssize_t ksu_kernel_write_compat(struct file *p, const void *buf,
2323
size_t count, loff_t *pos);
2424

25+
extern int ksu_access_ok(const void *addr, unsigned long size);
26+
2527
#endif

kernel/sucompat.c

Lines changed: 69 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -48,50 +48,91 @@ static char __user *ksud_user_path(void)
4848
return userspace_stack_buffer(ksud_path, sizeof(ksud_path));
4949
}
5050

51-
int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode,
52-
int *__unused_flags)
51+
static long ksu_strncpy_from_user_retry(char *dst,
52+
const void __user *unsafe_addr, long count)
53+
{
54+
long ret = ksu_strncpy_from_user_nofault(dst, unsafe_addr, count);
55+
if (likely(ret >= 0))
56+
return ret;
57+
58+
// we faulted! fallback to slow path
59+
if (unlikely(!ksu_access_ok(unsafe_addr, count)))
60+
return -EFAULT;
61+
62+
return strncpy_from_user(dst, unsafe_addr, count);
63+
}
64+
65+
// every little bit helps here
66+
__attribute__((hot, no_stack_protector))
67+
static __always_inline bool is_su_allowed(const void *ptr_to_check)
68+
{
69+
barrier();
70+
71+
if (likely(!ksu_is_allow_uid(current_uid().val)))
72+
return false;
73+
74+
if (unlikely(!ptr_to_check))
75+
return false;
76+
77+
return true;
78+
}
79+
80+
static int ksu_sucompat_user_common(const char __user **filename_user,
81+
const char *syscall_name,
82+
const bool escalate)
5383
{
5484
const char su[] = SU_PATH;
5585

56-
if (!ksu_is_allow_uid(current_uid().val)) {
86+
char path[sizeof(su)]; // sizeof includes nullterm already!
87+
long len = ksu_strncpy_from_user_retry(path, *filename_user, sizeof(path));
88+
if (len <= 0) // sizeof(su) is not zero
5789
return 0;
58-
}
5990

60-
char path[sizeof(su) + 1];
61-
memset(path, 0, sizeof(path));
62-
ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path));
91+
path[sizeof(path) - 1] = '\0';
6392

64-
if (unlikely(!memcmp(path, su, sizeof(su)))) {
65-
pr_info("faccessat su->sh!\n");
93+
if (memcmp(path, su, sizeof(su)))
94+
return 0;
95+
96+
if (escalate) {
97+
pr_info("%s su found\n", syscall_name);
98+
*filename_user = ksud_user_path();
99+
escape_to_root(); // escalate !!
100+
} else {
101+
pr_info("%s su->sh!\n", syscall_name);
66102
*filename_user = sh_user_path();
67103
}
68104

69105
return 0;
70106
}
71107

72-
int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags)
108+
// sys_faccessat
109+
int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode,
110+
int *__unused_flags)
73111
{
74-
// const char sh[] = SH_PATH;
75-
const char su[] = SU_PATH;
76-
77-
if (!ksu_is_allow_uid(current_uid().val)) {
112+
if (!is_su_allowed((const void *)filename_user))
78113
return 0;
79-
}
80114

81-
if (unlikely(!filename_user)) {
115+
return ksu_sucompat_user_common(filename_user, "faccessat", false);
116+
}
117+
118+
// sys_newfstatat, sys_fstat64
119+
int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags)
120+
{
121+
if (!is_su_allowed((const void *)filename_user))
82122
return 0;
83-
}
84123

85-
char path[sizeof(su) + 1];
86-
memset(path, 0, sizeof(path));
87-
ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path));
124+
return ksu_sucompat_user_common(filename_user, "newfstatat", false);
125+
}
88126

89-
if (unlikely(!memcmp(path, su, sizeof(su)))) {
90-
pr_info("newfstatat su->sh!\n");
91-
*filename_user = sh_user_path();
92-
}
127+
// sys_execve, compat_sys_execve
128+
int ksu_handle_execve_sucompat(int *fd, const char __user **filename_user,
129+
void *__never_use_argv, void *__never_use_envp,
130+
int *__never_use_flags)
131+
{
132+
if (!is_su_allowed((const void *)filename_user))
133+
return 0;
93134

94-
return 0;
135+
return ksu_sucompat_user_common(filename_user, "sys_execve", true);
95136
}
96137

97138
// the call from execve_handler_pre won't provided correct value for __never_use_argument, use them after fix execve_handler_pre, keeping them for consistence for manually patched code
@@ -103,7 +144,7 @@ int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr,
103144
const char sh[] = KSUD_PATH;
104145
const char su[] = SU_PATH;
105146

106-
if (unlikely(!filename_ptr))
147+
if (!is_su_allowed((const void *)filename_ptr))
107148
return 0;
108149

109150
filename = *filename_ptr;
@@ -114,9 +155,6 @@ int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr,
114155
if (likely(memcmp(filename->name, su, sizeof(su))))
115156
return 0;
116157

117-
if (!ksu_is_allow_uid(current_uid().val))
118-
return 0;
119-
120158
pr_info("do_execveat_common su found\n");
121159
memcpy((void *)filename->name, sh, sizeof(sh));
122160

@@ -125,35 +163,10 @@ int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr,
125163
return 0;
126164
}
127165

128-
int ksu_handle_execve_sucompat(int *fd, const char __user **filename_user,
129-
void *__never_use_argv, void *__never_use_envp,
130-
int *__never_use_flags)
131-
{
132-
const char su[] = SU_PATH;
133-
char path[sizeof(su) + 1];
134-
135-
if (unlikely(!filename_user))
136-
return 0;
137-
138-
memset(path, 0, sizeof(path));
139-
ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path));
140-
141-
if (likely(memcmp(path, su, sizeof(su))))
142-
return 0;
143-
144-
if (!ksu_is_allow_uid(current_uid().val))
145-
return 0;
146-
147-
pr_info("sys_execve su found\n");
148-
*filename_user = ksud_user_path();
149-
150-
escape_to_root();
151-
152-
return 0;
153-
}
154-
155166
int ksu_handle_devpts(struct inode *inode)
156167
{
168+
barrier();
169+
157170
if (!current->mm) {
158171
return 0;
159172
}

0 commit comments

Comments
 (0)