Skip to content

Commit 72c5662

Browse files
backslashxxanzarfarooq
authored andcommitted
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 d46d0a4 commit 72c5662

File tree

3 files changed

+80
-90
lines changed

3 files changed

+80
-90
lines changed

kernel/kernel_compat.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,3 +183,12 @@ long ksu_strncpy_from_user_nofault(char *dst, const void __user *unsafe_addr,
183183
return ret;
184184
}
185185
#endif
186+
187+
int ksu_access_ok(const void *addr, unsigned long size)
188+
{
189+
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,0,0)
190+
return access_ok(addr, size);
191+
#else
192+
return access_ok(VERIFY_READ, addr, size);
193+
#endif
194+
}

kernel/kernel_compat.h

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

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

kernel/sucompat.c

Lines changed: 69 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -51,136 +51,113 @@ static inline char __user *ksud_user_path(void)
5151
return userspace_stack_buffer(ksud_path, sizeof(ksud_path));
5252
}
5353

54-
int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode,
55-
int *__unused_flags)
54+
static long ksu_strncpy_from_user_retry(char *dst,
55+
const void __user *unsafe_addr, long count)
5656
{
57-
#ifndef CONFIG_KSU_SUSFS_SUS_SU
58-
if (!ksu_is_allow_uid(current_uid().val)) {
59-
return 0;
60-
}
61-
#endif
57+
long ret = ksu_strncpy_from_user_nofault(dst, unsafe_addr, count);
58+
if (likely(ret >= 0))
59+
return ret;
6260

63-
#ifdef CONFIG_KSU_SUSFS_SUS_SU
64-
char path[sizeof(su) + 1] = {0};
65-
#else
66-
char path[sizeof(su) + 1];
67-
memset(path, 0, sizeof(path));
68-
#endif
69-
ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path));
61+
// we faulted! fallback to slow path
62+
if (unlikely(!ksu_access_ok(unsafe_addr, count)))
63+
return -EFAULT;
7064

71-
if (unlikely(!memcmp(path, su, sizeof(su)))) {
72-
pr_info("faccessat su->sh!\n");
73-
*filename_user = sh_user_path();
74-
}
75-
76-
return 0;
65+
return strncpy_from_user(dst, unsafe_addr, count);
7766
}
7867

79-
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) && defined(CONFIG_KSU_SUSFS_SUS_SU)
80-
struct filename* susfs_ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags) {
81-
struct filename *name = getname_flags(*filename_user, getname_statx_lookup_flags(*flags), NULL);
68+
// every little bit helps here
69+
__attribute__((hot, no_stack_protector))
70+
static __always_inline bool is_su_allowed(const void *ptr_to_check)
71+
{
72+
barrier();
8273

83-
if (unlikely(IS_ERR(name) || name->name == NULL)) {
84-
return name;
85-
}
74+
if (likely(!ksu_is_allow_uid(current_uid().val)))
75+
return false;
8676

87-
if (likely(memcmp(name->name, su, sizeof(su)))) {
88-
return name;
89-
}
77+
if (unlikely(!ptr_to_check))
78+
return false;
9079

91-
const char sh[] = SH_PATH;
92-
pr_info("vfs_fstatat su->sh!\n");
93-
memcpy((void *)name->name, sh, sizeof(sh));
94-
return name;
80+
return true;
9581
}
96-
#endif
9782

98-
int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags)
83+
static int ksu_sucompat_user_common(const char __user **filename_user,
84+
const char *syscall_name,
85+
const bool escalate)
9986
{
100-
#ifndef CONFIG_KSU_SUSFS_SUS_SU
101-
if (!ksu_is_allow_uid(current_uid().val)) {
102-
return 0;
103-
}
104-
#endif
87+
const char su[] = SU_PATH;
10588

106-
if (unlikely(!filename_user)) {
89+
char path[sizeof(su)]; // sizeof includes nullterm already!
90+
long len = ksu_strncpy_from_user_retry(path, *filename_user, sizeof(path));
91+
if (len <= 0) // sizeof(su) is not zero
10792
return 0;
108-
}
10993

110-
#ifdef CONFIG_KSU_SUSFS_SUS_SU
111-
char path[sizeof(su) + 1] = {0};
112-
#else
113-
char path[sizeof(su) + 1];
114-
memset(path, 0, sizeof(path));
115-
#endif
116-
ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path));
94+
path[sizeof(path) - 1] = '\0';
11795

118-
if (unlikely(!memcmp(path, su, sizeof(su)))) {
119-
pr_info("newfstatat su->sh!\n");
96+
if (memcmp(path, su, sizeof(su)))
97+
return 0;
98+
99+
if (escalate) {
100+
pr_info("%s su found\n", syscall_name);
101+
*filename_user = ksud_user_path();
102+
escape_to_root(); // escalate !!
103+
} else {
104+
pr_info("%s su->sh!\n", syscall_name);
120105
*filename_user = sh_user_path();
121106
}
122107

123108
return 0;
124109
}
125110

126-
// 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
127-
int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr,
128-
void *__never_use_argv, void *__never_use_envp,
129-
int *__never_use_flags)
111+
// sys_faccessat
112+
int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode,
113+
int *__unused_flags)
130114
{
131-
struct filename *filename;
132-
133-
if (unlikely(!filename_ptr))
115+
if (!is_su_allowed((const void *)filename_user))
134116
return 0;
135117

136-
filename = *filename_ptr;
137-
if (IS_ERR(filename)) {
138-
return 0;
139-
}
140-
141-
if (likely(memcmp(filename->name, su, sizeof(su))))
142-
return 0;
118+
return ksu_sucompat_user_common(filename_user, "faccessat", false);
119+
}
143120

144-
#ifndef CONFIG_KSU_SUSFS_SUS_SU
145-
if (!ksu_is_allow_uid(current_uid().val))
121+
// sys_newfstatat, sys_fstat64
122+
int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags)
123+
{
124+
if (!is_su_allowed((const void *)filename_user))
146125
return 0;
147-
#endif
148-
149-
pr_info("do_execveat_common su found\n");
150-
memcpy((void *)filename->name, ksud_path, sizeof(ksud_path));
151126

152-
escape_to_root();
153-
154-
return 0;
127+
return ksu_sucompat_user_common(filename_user, "newfstatat", false);
155128
}
156129

130+
// sys_execve, compat_sys_execve
157131
int ksu_handle_execve_sucompat(int *fd, const char __user **filename_user,
158132
void *__never_use_argv, void *__never_use_envp,
159133
int *__never_use_flags)
160134
{
161-
//const char su[] = SU_PATH;
162-
#ifdef CONFIG_KSU_SUSFS_SUS_SU
163-
char path[sizeof(su) + 1] = {0};
164-
#else
165-
char path[sizeof(su) + 1];
166-
#endif
167-
168-
if (unlikely(!filename_user))
135+
if (!is_su_allowed((const void *)filename_user))
169136
return 0;
170137

171-
#ifndef CONFIG_KSU_SUSFS_SUS_SU
172-
memset(path, 0, sizeof(path));
173-
#endif
174-
ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path));
138+
return ksu_sucompat_user_common(filename_user, "sys_execve", true);
139+
}
140+
141+
// 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
142+
int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr,
143+
void *__never_use_argv, void *__never_use_envp,
144+
int *__never_use_flags)
145+
{
146+
struct filename *filename;
175147

176-
if (likely(memcmp(path, su, sizeof(su))))
148+
if (!is_su_allowed((const void *)filename_ptr))
177149
return 0;
178150

179-
if (!ksu_is_allow_uid(current_uid().val))
151+
filename = *filename_ptr;
152+
if (IS_ERR(filename)) {
180153
return 0;
154+
}
181155

182-
pr_info("sys_execve su found\n");
183-
*filename_user = ksud_user_path();
156+
if (likely(memcmp(filename->name, su, sizeof(su))))
157+
return 0;
158+
159+
pr_info("do_execveat_common su found\n");
160+
memcpy((void *)filename->name, ksud_path, sizeof(ksud_path));
184161

185162
escape_to_root();
186163

@@ -189,6 +166,8 @@ int ksu_handle_execve_sucompat(int *fd, const char __user **filename_user,
189166

190167
int ksu_handle_devpts(struct inode *inode)
191168
{
169+
barrier();
170+
192171
if (!current->mm) {
193172
return 0;
194173
}

0 commit comments

Comments
 (0)