+ syscalls-x86-add-__nr_kcmp-syscall-v8.patch added to -mm tree
Review
Quoted 8 lines
(quoted diff) +8
-0+/* The caller must have pinned the task */ +static struct file * +get_file_raw_ptr(struct task_struct *task, unsigned int idx) +{ + struct fdtable *fdt; + struct file *file; + + spin_lock(&task->files->file_lock);
task->files can be NULL, we can race with exit_files().
Review
Quoted 3 lines
(quoted diff) +3
-0+ fdt = files_fdtable(task->files); + if (idx < fdt->max_fds) + file = fdt->fd[idx];
You can probably rely on rcu instead of ->file_lock, but this is minor.
Quoted 17 lines
(quoted diff) +17
-0+SYSCALL_DEFINE5(kcmp, pid_t, pid1, pid_t, pid2, int, type, + unsigned long, idx1, unsigned long, idx2) +{ + struct task_struct *task1, *task2; + int ret; + + rcu_read_lock(); + + /* + * Tasks are looked up in caller's PID namespace only. + */ + task1 = find_task_by_vpid(pid1); + task2 = find_task_by_vpid(pid2); + if (!task1 || !task2) + goto err_no_task; + + get_task_struct(task1);
Review
Quoted 10 lines
(quoted diff) +10
-0+ get_task_struct(task2); + + rcu_read_unlock(); + + /* + * One should have enough rights to inspect task details. + */ + if (!ptrace_may_access(task1, PTRACE_MODE_READ) || + !ptrace_may_access(task2, PTRACE_MODE_READ)) { + ret = -EACCES;
Well, probably this is fine... but may be you can add a comment. The task can change its credentials right after ptrace_may_access() succeeds. This _looks_ wrong, perhaps it makes sense to add the "we do not care" note.
Oleg.
Quoted 2 lines · Oleg Nesterov
task->filescan be NULL, we can race withexit_files().
So I need to call get_files_struct. Thanks Oleg!
Quoted 7 lines
Review
Quoted 4 lines
(quoted diff) +3
-0+ fdt = files_fdtable(task->files); + if (idx < fdt->max_fds) + file = fdt->fd[idx];You can probably rely on rcu instead of ->file_lock, but this is minor.
I think so. I'll be updating the patch anyway (on top of current one) so I'll address this comment too. Thanks!
Quoted 4 lines
Quoted 4 lines
(quoted diff) +4
-0+ + /* + * One should have enough rights to inspect task details. + */
Quoted 9 lines
Review
Quoted 3 lines
(quoted diff) +3
-0+ if (!ptrace_may_access(task1, PTRACE_MODE_READ) || + !ptrace_may_access(task2, PTRACE_MODE_READ)) { + ret = -EACCES;Well, probably this is fine... but may be you can add a comment. The task can change its credentials right after
ptrace_may_access()succeeds. This _looks_ wrong, perhaps it makes sense to add the "we do not care" note.
Wait, how it's differ from other ptrace_may_access calls all over the kernel? I suppose I'm missing something obvious?
Cyrill
Quoted 17 lines · Cyrill Gorcunov
Quoted 14 lines · Oleg Nesterov
Review
Quoted 8 lines
(quoted diff) +7
-0+ + /* + * One should have enough rights to inspect task details. + */ + if (!ptrace_may_access(task1, PTRACE_MODE_READ) || + !ptrace_may_access(task2, PTRACE_MODE_READ)) { + ret = -EACCES;Well, probably this is fine... but may be you can add a comment. The task can change its credentials right after
ptrace_may_access()succeeds. This _looks_ wrong, perhaps it makes sense to add the "we do not care" note.
Quoted 2 lines
Wait, how it's differ from other
ptrace_may_accesscalls all over the kernel? I suppose I'm missing something obvious?
For example? Say, mm_access() is fine because it returns ->mm which we are going to play with.
But map_files_d_revalidate/proc_map_files_get_link looks wrong, there are obviously racy and should use mm_access().
Probably something else is wrong too.
Once again, I am not saying that this code really has the security problems. I simply do not know. But it looks wrong without the comment. I do not really understand why do we need ptrace_may_access(), but whatever reason we have how we can trust it? Say, when KCMP_VM checks ->mm, all we know is that PTRACE_MODE_READ succeed in the past. This looks confusing, imho.
Oleg.
On Wed, Feb 15, 2012 at 04:38:16PM +0100, Oleg Nesterov wrote:
...
Quoted 6 lines
Quoted 3 lines
Wait, how it's differ from other
ptrace_may_accesscalls all over the kernel? I suppose I'm missing something obvious?For example? Say,
mm_access()is fine because it returns ->mm which we are going to play with.
So, say we have
environ_read
mm_for_maps
mm_access
success, and first reader continue here
then say task change own credentials and all
this sequence fails because access is not granted anymore (say for second reader), but first reader still able to continue reading because access was graned earlier.
So I don't understand how it's different from what is provided in this patch. What I'm missing?
Quoted 6 lines
Once again, I am not saying that this code really has the security problems. I simply do not know. But it looks wrong without the comment. I do not really understand why do we need
ptrace_may_access(), but whatever reason we have how we can trust it? Say, whenKCMP_VMchecks ->mm, all we know is thatPTRACE_MODE_READsucceed in the past. This looks confusing, imho.
Adding the comment is not a problem. The problem is that I dont understand if there error in patch or not, can we stick with ptrace_may_access or need something different here? The idea was exactly like -- if you have enough rights to proceed ptrace_may_access then syscall should continue executing and return comparision result.
Cyrill
Not a comment, but the question. I am just curious...
Review
Quoted 12 lines
(quoted diff) +12
-0+/* + * We don't expose real in-memory order of objects for security + * reasons, still the comparison results should be suitable for + * sorting. Thus, we obfuscate kernel pointers values and compare + * the production instead. + */ +static unsigned long cookies[KCMP_TYPES][2] __read_mostly; + +static long kptr_obfuscate(long v, int type) +{ + return (v ^ cookies[type][0]) * cookies[type][1]; +}
OK, but why do we need this per type? Just to add more obfuscation or there is another reason?
Review
Quoted 8 lines
(quoted diff) +8
-0+static __init int kcmp_cookies_init(void) +{ + int i; + + get_random_bytes(cookies, sizeof(cookies)); + + for (i = 0; i < KCMP_TYPES; i++) + cookies[i][1] |= (~(~0UL >> 1) | 1);
I am puzzled, help ;) this is equal to
cookies[i][1] |= -LONG_MAX; or cookies[i][1] |= (LONG_MIN | 1);
for what? why do we want to set these 2 bits (MSB and LSB) ?
Oleg.
Quoted 17 lines · Oleg Nesterov
Not a comment, but the question. I am just curious...
Review
Quoted 12 lines
(quoted diff) +12
-0+/* + * We don't expose real in-memory order of objects for security + * reasons, still the comparison results should be suitable for + * sorting. Thus, we obfuscate kernel pointers values and compare + * the production instead. + */ +static unsigned long cookies[KCMP_TYPES][2] __read_mostly; + +static long kptr_obfuscate(long v, int type) +{ + return (v ^ cookies[type][0]) * cookies[type][1]; +}OK, but why do we need this per type? Just to add more obfuscation or there is another reason?
Just to add more obfuscation.
Quoted 12 lines
Review
Quoted 9 lines
(quoted diff) +8
-0+static __init int kcmp_cookies_init(void) +{ + int i; + + get_random_bytes(cookies, sizeof(cookies)); + + for (i = 0; i < KCMP_TYPES; i++) + cookies[i][1] |= (~(~0UL >> 1) | 1);I am puzzled, help ;) this is equal to
Quoted 5 lines
cookies[i][1] |= -LONG_MAX; or cookies[i][1] |= (LONG_MIN | 1);for what? why do we want to set these 2 bits (MSB and LSB) ?
Letme quote hpa@ here :)
| This code is wrong. You will have a zero cookie, legitimately, once in | 2^32 or 2^64 attempts, depending on the bitness. | | The other thing is that for the multiplicative cookie you should OR in | the value (~(~0UL >> 1) | 1) in order to make sure that the value is (a) | large and (b) odd.
Cyrill
Quoted 17 lines · Cyrill Gorcunov
On Wed, Feb 15, 2012 at 04:38:16PM +0100, Oleg Nesterov wrote:
...Quoted 6 lines
Quoted 3 lines
Wait, how it's differ from other
ptrace_may_accesscalls all over the kernel? I suppose I'm missing something obvious?For example? Say,
mm_access()is fine because it returns ->mm which we are going to play with.So, say we have
environ_read
mm_for_maps
mm_access
success, and first reader continue here
Quoted 5 lines
then say task change own credentials and all
this sequence fails because access is not granted anymore (say for second reader), but first reader still able to continue reading because access was graned earlier.
Can't understand... Yes, environ_read() can succeed for the first reader, and then later it can fail for the same/another reader. And?
Quoted 2 lines
So I don't understand how it's different from what is provided in this patch. What I'm missing?
environ_read() does
mm = mm_access(task); if (mm) do_something(mm);
even if it races with, say, execve(setuid_app) we can't read the new ->mm.
while your code (very roughly) does something like
mm = mm_access(task); if (mm) do_something(task->mm);
while it is quite possible that mm != task->mm.
Quoted 7 lines
Quoted 6 lines
Once again, I am not saying that this code really has the security problems. I simply do not know. But it looks wrong without the comment. I do not really understand why do we need
ptrace_may_access(), but whatever reason we have how we can trust it? Say, whenKCMP_VMchecks ->mm, all we know is thatPTRACE_MODE_READsucceed in the past. This looks confusing, imho.
Quoted 6 lines
Adding the comment is not a problem. The problem is that I dont understand if there error in patch or not, can we stick with
ptrace_may_accessor need something different here? The idea was exactly like -- if you have enough rights to proceedptrace_may_accessthen syscall should continue executing and return comparision result.
My only point is: this check is obviously racy, and thus it looks confusing. Whether this is fine or not, I do not know. Personally I see no reason for ptrace_may_access(), but I am not security expert.
Oleg.
Quoted 4 lines · Oleg Nesterov
Quoted 3 lines
So I don't understand how it's different from what is provided in this patch. What I'm missing?
Quoted 8 lines
environ_read()doesmm = mm_access(task); if (mm) do_something(mm);even if it races with, say,
execve(setuid_app)we can't read the new ->mm.
Wait, I'm confused
process 1 (reader) process 2 ("task" itself)
mm = mm_access(task);
task changes own credentials so reader can't access on next read if it would try, but since access already granted... it continues do_something(mm)
if (mm)
do_something(mm);
So in the patch I tried the same, once access is granted it belongs to a caller.
Quoted 8 lines
while your code (very roughly) does something like
mm = mm_access(task); if (mm) do_something(task->mm);while it is quite possible that mm !=
task->mm.
Oleg, could you please explain me where it happens that task->mm (I've got access to) will be changed to some new -mm while I'm inspecting it.
If permission changed while the caller inside syscall, it's the same situation as with mm_access above. No?
Quoted 5 lines
My only point is: this check is obviously racy, and thus it looks confusing. Whether this is fine or not, I do not know. Personally I see no reason for
ptrace_may_access(), but I am not security expert.
The idea was -- non-privilege caller should not have access to privileged tasks.
Cyrill
Quoted 6 lines · Cyrill Gorcunov
Quoted 2 lines · Oleg Nesterov
task->filescan be NULL, we can race withexit_files().So I need to call
get_files_struct. Thanks Oleg!
Something like below I think, right? (it seems this patc doesn't hit lenux-next yet, so here is interdiff output)
Cyrill
---
kernel/kcmp.c +12 -3
@@ -44,16 +44,25 @@static struct file *get_file_raw_ptr (struct task_struct *task,unsigned int idx) { +struct files_struct *files;struct fdtable *fdt;struct file *file; -spin_lock (&task->files->file_lock); - fdt =files_fdtable (task->files); + files =get_files_struct (task); +if (!files) +return NULL ; + +rcu_read_lock (); + + fdt =files_fdtable (files);if (idx < fdt->max_fds) file = fdt->fd[idx];else file =NULL ; -spin_unlock (&task->files->file_lock); + +rcu_read_unlock (); + +put_files_struct (files);return file; }
Quoted 27 lines · Cyrill Gorcunov
Quoted 4 lines · Oleg Nesterov
Quoted 3 lines
So I don't understand how it's different from what is provided in this patch. What I'm missing?
Quoted 8 lines
environ_read()doesmm = mm_access(task); if (mm) do_something(mm);even if it races with, say,
execve(setuid_app)we can't read the new ->mm.Wait, I'm confused
process 1 (reader) process 2 ("task" itself)
mm =mm_access(task);
task changes own credentials so reader can't access on next read if it would try, but since access already granted... it continuesdo_something(mm)
if (mm)
do_something(mm);
Quoted 2 lines
So in the patch I tried the same, once access is granted it belongs to a caller.
See the "execve(setuid_app)", this is what I meant. Even if we race with execve() and the task raises its privileges we can't read the new ->mm, we will read the old mm for which we have (had) the rights to access.
Quoted 11 lines
Quoted 7 lines
while your code (very roughly) does something like
mm = mm_access(task); if (mm) do_something(task->mm);while it is quite possible that mm !=
task->mm.Oleg, could you please explain me where it happens that
task->mm(I've got access to) will be changed to some new -mm while I'm inspecting it.
Cough... this is question I am trying to ask ;)
Let me try again. To simplify, lets discuss the KCMP_VM case only.
I do not really understand why do we need ptrace_may_access(). I do not see any security problems with kcmp_ptr(task->mm), but I am not expert.
However, you added this check so I assume you have some reason. But this can race with execve(setuid_app) and KCMP_VM can play with task->mm after this task raises its caps. If this is fine, then why do we need ptrace_may_access?
OK, please ignore. I sent the initial email just becase KCMP_FILE is buggy.
Quoted 11 lines
Quoted 10 lines
Review
Quoted 2 lines
(quoted diff) +2
-0+ for (i = 0; i < KCMP_TYPES; i++) + cookies[i][1] |= (~(~0UL >> 1) | 1);I am puzzled, help ;) this is equal to
cookies[i][1] |= -LONG_MAX; or cookies[i][1] |= (LONG_MIN | 1);for what? why do we want to set these 2 bits (MSB and LSB) ?
Quoted 8 lines
Letme quote hpa@ here :)
| This code is wrong. You will have a zero cookie, legitimately, once in | 2^32 or 2^64 attempts, depending on the bitness. | | The other thing is that for the multiplicative cookie you should OR in | the value (~(~0UL >> 1) | 1) in order to make sure that the value is (a) | large and (b) odd.
OK, thanks.
Oleg.
Quoted 34 lines · Cyrill Gorcunov
Quoted 6 lines · Cyrill Gorcunov
Quoted 2 lines · Oleg Nesterov
task->filescan be NULL, we can race withexit_files().So I need to call
get_files_struct. Thanks Oleg!Something like below I think, right? (it seems this patc doesn't hit lenux-next yet, so here is interdiff output)
Cyrill
---
diff -ulinux-2.6.git/kernel/kcmp.clinux-2.6.git/kernel/kcmp.ckernel/kcmp.c +7
-2@@ -44,16 +44,25 @@static struct file *get_file_raw_ptr (struct task_struct *task,unsigned int idx) { +struct files_struct *files;struct fdtable *fdt;struct file *file; -spin_lock (&task->files->file_lock); - fdt =files_fdtable (task->files); + files =get_files_struct (task); +if (!files) +return NULL ; + +rcu_read_lock (); +
kernel/kcmp.c line 65
Quoted 10 lines
kernel/kcmp.c +5
-1+ fdt =files_fdtable (files);if (idx < fdt->max_fds) file = fdt->fd[idx];else file =NULL ; -spin_unlock (&task->files->file_lock); + +rcu_read_unlock (); + +put_files_struct (files);
I think this should work. Or you can simply do
struct file *file = NULL; task_lock(task); rcu_read_lock(); if (task->files) file = fcheck_files(task->files, idx); rcu_read_unlock(); task_unlock(task);
Oleg.
Quoted 12 lines · Oleg Nesterov
I think this should work. Or you can simply do
struct file *file = NULL; task_lock(task); rcu_read_lock(); if (task->files) file = fcheck_files(task->files, idx); rcu_read_unlock(); task_unlock(task);
Yeah, thanks, this one is better.
Cyrill
On Wed, Feb 15, 2012 at 07:43:36PM +0100, Oleg Nesterov wrote:
...
Quoted 6 lines
Cough... this is question I am trying to ask ;)
Let me try again. To simplify, lets discuss the
KCMP_VMcase only.
Quoted 9 lines
I do not really understand why do we need
ptrace_may_access(). I do not see any security problems withkcmp_ptr(task->mm), but I am not expert.However, you added this check so I assume you have some reason. But this can race with
execve(setuid_app)andKCMP_VMcan play withtask->mmafter this task raises its caps. If this is fine, then why do we needptrace_may_access?
This makes me scratch the head ;) I think ptrace_may_access (or some other security test) should remain since it's somehow weird if non-root task will be able to find objects order from privileged task. Thus I need to find a way how to handle execve(setuid_app). Need to think...
Cyrill
Quoted 18 lines · Cyrill Gorcunov
On Wed, Feb 15, 2012 at 07:43:36PM +0100, Oleg Nesterov wrote:
...Quoted 6 lines
Cough... this is question I am trying to ask ;)
Let me try again. To simplify, lets discuss the
KCMP_VMcase only.Quoted 9 lines
I do not really understand why do we need
ptrace_may_access(). I do not see any security problems withkcmp_ptr(task->mm), but I am not expert.However, you added this check so I assume you have some reason. But this can race with
execve(setuid_app)andKCMP_VMcan play withtask->mmafter this task raises its caps. If this is fine, then why do we needptrace_may_access?
Quoted 5 lines
This makes me scratch the head ;) I think
ptrace_may_access(or some other security test) should remain since it's somehow weird if non-root task will be able to find objects order from privileged task. Thus I need to find a way how to handleexecve(setuid_app). Need to think...
Look at fs/proc/base.c:lock_trace() - it locks ->cred_guard_mutex for the whole period of time when it uses a resource.
Signature · 2 lines
Vasiliy Kulikov http://www.openwall.com - bringing security into open computing environments
Quoted 9 lines · Vasiliy Kulikov
Quoted 6 lines
This makes me scratch the head ;) I think
ptrace_may_access(or some other security test) should remain since it's somehow weird if non-root task will be able to find objects order from privileged task. Thus I need to find a way how to handleexecve(setuid_app). Need to think...Look at fs/proc/base.c:lock_trace() - it locks ->cred_guard_mutex for the whole period of time when it uses a resource.
Yup, thanks Vasiliy! I've just found cred_guard_mutex in install_exec_creds. Now I'm thinking if this is what we need here ;)
Cyrill
Quoted 8 lines · Cyrill Gorcunov
Quoted 7 lines · Vasiliy Kulikov
Quoted 6 lines
This makes me scratch the head ;) I think
ptrace_may_access(or some other security test) should remain since it's somehow weird if non-root task will be able to find objects order from privileged task. Thus I need to find a way how to handleexecve(setuid_app). Need to think...
Quoted 7 lines
Quoted 2 lines
Look at fs/proc/base.c:lock_trace() - it locks ->cred_guard_mutex for the whole period of time when it uses a resource.
Yup, thanks Vasiliy! I've just found
cred_guard_mutexininstall_exec_creds. Now I'm thinking if this is what we need here ;)
Something like below I think (not yet tested, overall update).
Cyrill
---
kernel/kcmp.c +31 -13
@@ -44,20 +44,34 @@static struct file *get_file_raw_ptr (struct task_struct *task,unsigned int idx) { -struct fdtable *fdt; -struct file *file; +struct file *file =NULL ; -spin_lock (&task->files->file_lock); - fdt =files_fdtable (task->files); -if (idx < fdt->max_fds) - file = fdt->fd[idx]; -else - file =NULL ; -spin_unlock (&task->files->file_lock); +task_lock (task); +rcu_read_lock (); + +if (task->files) + file =fcheck_files (task->files, idx); + +rcu_read_unlock (); +task_unlock (task);return file; } +static int may_access (struct task_struct *task) +{ +int err =mutex_lock_killable (&task->signal->cred_guard_mutex); +if (err) +return err; + +if (!ptrace_may_access (task,PTRACE_MODE_READ )) { +mutex_unlock (&task->signal->cred_guard_mutex); +return -EPERM ; + } + +return 0 ; +} +SYSCALL_DEFINE5 (kcmp, pid_t, pid1, pid_t, pid2, int, type, unsigned long, idx1, unsigned long, idx2) {
@@ -82,11 +96,12 @@/* * One should have enough rights to inspect task details. */ -if (!ptrace_may_access (task1,PTRACE_MODE_READ ) || - !ptrace_may_access (task2,PTRACE_MODE_READ )) { - ret = -EACCES ; + ret =may_access (task1); +if (ret) goto err; - } + ret =may_access (task2); +if (ret) + goto err_unlock;switch (type) {case KCMP_FILE : {
@@ -130,6 +145,9 @@break ; } +mutex_unlock (&task2->signal->cred_guard_mutex); +err_unlock: +mutex_unlock (&task1->signal->cred_guard_mutex); err:put_task_struct (task1);put_task_struct (task2);
Quoted 3 lines · Cyrill Gorcunov
Something like below I think (not yet tested, overall update).
OK, this one I've just tested. Please review, so I won't miss something obvious (I'm fetching linux-next now, if the patch is already there I'll cook one on top).
Cyrill
---
kernel/kcmp.c +35 -13
@@ -44,20 +44,38 @@static struct file *get_file_raw_ptr (struct task_struct *task,unsigned int idx) { -struct fdtable *fdt; -struct file *file; +struct file *file =NULL ; -spin_lock (&task->files->file_lock); - fdt =files_fdtable (task->files); -if (idx < fdt->max_fds) - file = fdt->fd[idx]; -else - file =NULL ; -spin_unlock (&task->files->file_lock); +task_lock (task); +rcu_read_lock (); + +if (task->files) + file =fcheck_files (task->files, idx); + +rcu_read_unlock (); +task_unlock (task);return file; } +static void access_unlock (struct task_struct *task) +{ +mutex_unlock (&task->signal->cred_guard_mutex); +} + +static int access_trylock (struct task_struct *task) +{ +if (!mutex_trylock (&task->signal->cred_guard_mutex)) +return -EBUSY ; + +if (!ptrace_may_access (task,PTRACE_MODE_READ )) { +mutex_unlock (&task->signal->cred_guard_mutex); +return -EPERM ; + } + +return 0 ; +} +SYSCALL_DEFINE5 (kcmp, pid_t, pid1, pid_t, pid2, int, type, unsigned long, idx1, unsigned long, idx2) {
@@ -82,11 +100,12 @@/* * One should have enough rights to inspect task details. */ -if (!ptrace_may_access (task1,PTRACE_MODE_READ ) || - !ptrace_may_access (task2,PTRACE_MODE_READ )) { - ret = -EACCES ; + ret =access_trylock (task1); +if (ret) goto err; - } + ret =access_trylock (task2); +if (ret) + goto err_unlock;switch (type) {case KCMP_FILE : {
@@ -130,6 +149,9 @@break ; } +access_unlock (task2); +err_unlock: +access_unlock (task1); err:put_task_struct (task1);put_task_struct (task2);
Quoted 9 lines · Cyrill Gorcunov
Quoted 3 lines · Cyrill Gorcunov
Something like below I think (not yet tested, overall update).
OK, this one I've just tested. Please review, so I won't miss something obvious (I'm fetching linux-next now, if the patch is already there I'll cook one on top).
---
Unfortunately I dont see this patch in linux-next, so I cooked it as interdiff'ed. Please review.
Maybe (if this patch is fine) we could drop v8 and I would squash this changed into v9 (together with update to self test)?
Cyrill
From: Cyrill Gorcunov <[email protected]>
Subject: syscalls, x86: Fix __NR_kcmp execve race and potential NULL dereference
Plain ptrace_may_access() check used in kcmp is not safe against race with execve(setuid_app), so we need to grab cred_guard_mutex and keep it until kcmp is finished.
Also task->files may be nil, better to use task_lock and fcheck_files helpers instead of direct file_lock usage.
---
kernel/kcmp.c +35 -13
@@ -44,20 +44,38 @@static struct file *get_file_raw_ptr (struct task_struct *task,unsigned int idx) { -struct fdtable *fdt; -struct file *file; +struct file *file =NULL ; -spin_lock (&task->files->file_lock); - fdt =files_fdtable (task->files); -if (idx < fdt->max_fds) - file = fdt->fd[idx]; -else - file =NULL ; -spin_unlock (&task->files->file_lock); +task_lock (task); +rcu_read_lock (); + +if (task->files) + file =fcheck_files (task->files, idx); + +rcu_read_unlock (); +task_unlock (task);return file; } +static void access_unlock (struct task_struct *task) +{ +mutex_unlock (&task->signal->cred_guard_mutex); +} + +static int access_trylock (struct task_struct *task) +{ +if (!mutex_trylock (&task->signal->cred_guard_mutex)) +return -EBUSY ; + +if (!ptrace_may_access (task,PTRACE_MODE_READ )) { +mutex_unlock (&task->signal->cred_guard_mutex); +return -EPERM ; + } + +return 0 ; +} +SYSCALL_DEFINE5 (kcmp, pid_t, pid1, pid_t, pid2, int, type, unsigned long, idx1, unsigned long, idx2) {
@@ -82,11 +100,12 @@/* * One should have enough rights to inspect task details. */ -if (!ptrace_may_access (task1,PTRACE_MODE_READ ) || - !ptrace_may_access (task2,PTRACE_MODE_READ )) { - ret = -EACCES ; + ret =access_trylock (task1); +if (ret) goto err; - } + ret =access_trylock (task2); +if (ret) + goto err_unlock;switch (type) {case KCMP_FILE : {
@@ -130,6 +149,9 @@break ; } +access_unlock (task2); +err_unlock: +access_unlock (task1); err:put_task_struct (task1);put_task_struct (task2);
kernel/kcmp.c line 77
Quoted 13 lines · Cyrill Gorcunov
kernel/kcmp.c +12
-0+static int access_trylock (struct task_struct *task) +{ +if (!mutex_trylock (&task->signal->cred_guard_mutex)) +return -EBUSY ; + +if (!ptrace_may_access (task,PTRACE_MODE_READ )) { +mutex_unlock (&task->signal->cred_guard_mutex); +return -EPERM ; + } + +return 0 ; +}
OK, this looks correct, but I don't really understand _trylock. This means the caller should always retry if -EBUSY, and kcmp(pid, pid) can never succeed. Sure, kcmp() doesn't make a lot of sense if pid1 == pid2, but this looks a bit strange.
You could simply do
int mutex_double_lock_killable(struct mutex *m1, struct mutex *m2)
{
int err;
if (m2 > m1)
swap(m1, m2);
err = mutex_lock_killable(m1);
if (!err && likely(m1 != m2)) {
err = mutex_lock_killable_nested(m2);
if (err)
mutex_unlock(m1);
}
return err;
}but I won't unsist.
Oleg.
Quoted 11 lines · Oleg Nesterov
Quoted 10 lines · Cyrill Gorcunov
kernel/kcmp.c +9
-0+static int access_trylock (struct task_struct *task) +{ +if (!mutex_trylock (&task->signal->cred_guard_mutex)) +return -EBUSY ; + +if (!ptrace_may_access (task,PTRACE_MODE_READ )) { +mutex_unlock (&task->signal->cred_guard_mutex); +return -EPERM ; + }
Quoted 9 lines
kernel/kcmp.c line 77
Quoted 3 lines
kernel/kcmp.c +3
-0+ +return 0 ; +}OK, this looks correct, but I don't really understand
_trylock. This means the caller should always retry if-EBUSY, andkcmp(pid, pid)can never succeed. Sure,kcmp()doesn't make a lot of sense if pid1 == pid2, but this looks a bit strange.
Hi Oleg, sure I can make it this way, also I think if pid1 == pid2 and idx1 == idx2 I can return 0 immediately.
Quoted 19 lines
You could simply do
int mutex_double_lock_killable(struct mutex *m1, struct mutex *m2) { int err; if (m2 > m1) swap(m1, m2); err = mutex_lock_killable(m1); if (!err && likely(m1 != m2)) { err = mutex_lock_killable_nested(m2); if (err) mutex_unlock(m1); } return err; }
Quoted 1 line
but I won't insist.
Initially I wanted kcmp would be brining minimum impact and if mutex is already taken by someone, we would not sleep but return immediately with -EBUSY and it would be up to caller to deside if to try again or make some delay first. I simply not sure what is better here.
Cyrill
On Thu, Feb 16, 2012 at 07:13:40PM +0400, Cyrill Gorcunov wrote:
...
Quoted 20 lines
Quoted 20 lines
You could simply do
int mutex_double_lock_killable(struct mutex *m1, struct mutex *m2) { int err; if (m2 > m1) swap(m1, m2); err = mutex_lock_killable(m1); if (!err && likely(m1 != m2)) { err = mutex_lock_killable_nested(m2); if (err) mutex_unlock(m1); } return err; }
Quoted 8 lines
Quoted 1 line
but I won't insist.
Initially I wanted kcmp would be brining minimum impact and if mutex is already taken by someone, we would not sleep but return immediately with
-EBUSYand it would be up to caller to deside if to try again or make some delay first. I simply not sure what is better here.
This one should do the trick.
Cyrill
---
From: Cyrill Gorcunov <[email protected]>
Subject: syscalls, x86: Make __NR_kcmp to work with equivalent pids
In case if pid1 is equal to pid2 the kcmp will return -EBUSY, which makes no sence. Make it able to work with equivalent pids. Selftest is extended as well.
---
kernel/kcmp.c +26 -14
@@ -58,22 +58,31 @@return file; } -static void access_unlock (struct task_struct *task) +static void kcmp_unlock (struct mutex *m1,struct mutex *m2) { -mutex_unlock (&task->signal->cred_guard_mutex); +if (m2 > m1) +swap (m1, m2); + +if (likely (m2 != m1)) +mutex_unlock (m2); +mutex_unlock (m1); } -static int access_trylock (struct task_struct *task) +static int kcmp_lock (struct mutex *m1,struct mutex *m2) { -if (!mutex_trylock (&task->signal->cred_guard_mutex)) -return -EBUSY ; +int err; + +if (m2 > m1) +swap (m1, m2); -if (!ptrace_may_access (task,PTRACE_MODE_READ )) { -mutex_unlock (&task->signal->cred_guard_mutex); -return -EPERM ; + err =mutex_lock_killable (m1); +if (!err &&likely (m1 != m2)) { + err =mutex_lock_killable_nested (m2,SINGLE_DEPTH_NESTING ); +if (err) +mutex_unlock (m1); } -return 0 ; +return err; } SYSCALL_DEFINE5(kcmp, pid_t, pid1, pid_t, pid2, int, type,
@@ -100,12 +109,15 @@/* * One should have enough rights to inspect task details. */ - ret =access_trylock (task1); + ret =kcmp_lock (&task1->signal->cred_guard_mutex, + &task2->signal->cred_guard_mutex);if (ret) goto err; - ret =access_trylock (task2); -if (ret) +if (!ptrace_may_access (task1,PTRACE_MODE_READ ) || + !ptrace_may_access (task2,PTRACE_MODE_READ )) { + ret = -EPERM ; goto err_unlock; + }switch (type) {case KCMP_FILE : {
@@ -149,9 +161,9 @@break ; } -access_unlock (task2); err_unlock: -access_unlock (task1); +kcmp_unlock (&task1->signal->cred_guard_mutex, + &task2->signal->cred_guard_mutex); err:put_task_struct (task1);put_task_struct (task2);
tools/testing/selftests/kcmp/kcmp_test.c +9 -0
@@ -74,6 +74,15 @@ ret =-1 ; }else printf ("PASS: 0 returned as expected\n" ); + +/* Compare with self */ + ret =sys_kcmp (pid1, pid1,KCMP_VM ,0 ,0 ); +if (ret) { +printf ("FAIL: 0 expected but %li returned\n" , ret); + ret =-1 ; + }else +printf ("PASS: 0 returned as expected\n" ); +exit (ret); }
kernel/kcmp.c line 64
Quoted 7 lines · Cyrill Gorcunov
kernel/kcmp.c +3
-2-static void access_unlock (struct task_struct *task) +static void kcmp_unlock (struct mutex *m1,struct mutex *m2) { -mutex_unlock (&task->signal->cred_guard_mutex); +if (m2 > m1) +swap (m1, m2);
Well, the order doesn't matter in case of _unlock, you can remove this part. Not that it really hurts though, I won't argue.
Oleg.
Quoted 12 lines · Oleg Nesterov
kernel/kcmp.c line 64
Quoted 7 lines · Cyrill Gorcunov
kernel/kcmp.c +3
-2-static void access_unlock (struct task_struct *task) +static void kcmp_unlock (struct mutex *m1,struct mutex *m2) { -mutex_unlock (&task->signal->cred_guard_mutex); +if (m2 > m1) +swap (m1, m2);Well, the order doesn't matter in case of
_unlock, you can remove this part. Not that it really hurts though, I won't argue.
It drops some instructions so I think it worth removing (still unlocking not in reverse order is something which always make me nervious ;)
Cyrill
kernel/kcmp.c line 80
Quoted 3 lines · Cyrill Gorcunov
kernel/kcmp.c +3
-0+ err =mutex_lock_killable (m1); +if (!err &&likely (m1 != m2)) { + err =mutex_lock_killable_nested (m2,SINGLE_DEPTH_NESTING );
Doesn't it lead to a deadlock?
mutex_lock_killable(task1)
| mutex_lock_killable(task2)mutex_lock_killable_nested(task2) |
(locked) |
mutex_lock_killable_nested(task1) (locked)
I suppose you should use some global lock (kcmp_lock) before both locks.
Thanks,
Signature · 2 lines
Vasiliy Kulikov http://www.openwall.com - bringing security into open computing environments
Quoted 7 lines · Vasiliy Kulikov
kernel/kcmp.c line 80
Quoted 3 lines · Cyrill Gorcunov
kernel/kcmp.c +3
-0+ err =mutex_lock_killable (m1); +if (!err &&likely (m1 != m2)) { + err =mutex_lock_killable_nested (m2,SINGLE_DEPTH_NESTING );Doesn't it lead to a deadlock?
Quoted 8 lines
mutex_lock_killable(task1)
|mutex_lock_killable(task2)mutex_lock_killable_nested(task2)|
(locked) |
mutex_lock_killable_nested(task1)(locked)I suppose you should use some global lock (
kcmp_lock) before both locks.
but here is if (m1 > m2) and swap() do take place.
Cyrill
Quoted 15 lines · Cyrill Gorcunov
Quoted 14 lines · Vasiliy Kulikov
kernel/kcmp.c line 80
Quoted 3 lines · Cyrill Gorcunov
kernel/kcmp.c +3
-0+ err =mutex_lock_killable (m1); +if (!err &&likely (m1 != m2)) { + err =mutex_lock_killable_nested (m2,SINGLE_DEPTH_NESTING );Doesn't it lead to a deadlock?
mutex_lock_killable(task1)
|mutex_lock_killable(task2)mutex_lock_killable_nested(task2)|
(locked) |
mutex_lock_killable_nested(task1)(locked)
Quoted 3 lines
Quoted 1 line
I suppose you should use some global lock (
kcmp_lock) before both locks.but here is if (m1 > m2) and
swap()do take place.
Ah, ok. Then this deadlock scenario is impossible, sorry.
Signature · 2 lines
Vasiliy Kulikov http://www.openwall.com - bringing security into open computing environments
Quoted 14 lines · Cyrill Gorcunov
Quoted 11 lines · Oleg Nesterov
kernel/kcmp.c line 64
Quoted 7 lines · Cyrill Gorcunov
kernel/kcmp.c +3
-2-static void access_unlock (struct task_struct *task) +static void kcmp_unlock (struct mutex *m1,struct mutex *m2) { -mutex_unlock (&task->signal->cred_guard_mutex); +if (m2 > m1) +swap (m1, m2);Well, the order doesn't matter in case of
_unlock, you can remove this part. Not that it really hurts though, I won't argue.
Quoted 1 line
It drops some instructions so I think it worth removing
Yes.
Quoted 3 lines
(still
unlocking not in reverse order is something which always make me nervious ;)
Yes ;)
so let me repeat, I am not arguing. But IMHO every piece of code should be understandable. Personally I do not mind at all, just I _personally_ think this code _can_ confuse the reader, "damn why we can't simply unlock in any order???".
If you add the "not really needed" comment above this swap - I agree. If you simply remove this swap - I agree as well.
But. I won't argue if you prefer to keep this patch as is. You are the author. If it looks better to _you_ - OK, this is correct (afaics).
Oleg.
Quoted 8 lines · Vasiliy Kulikov
kernel/kcmp.c line 80
Quoted 3 lines · Cyrill Gorcunov
kernel/kcmp.c +3
-0+ err =mutex_lock_killable (m1); +if (!err &&likely (m1 != m2)) { + err =mutex_lock_killable_nested (m2,SINGLE_DEPTH_NESTING );Doesn't it lead to a deadlock?
Quoted 6 lines
mutex_lock_killable(task1)
|mutex_lock_killable(task2)mutex_lock_killable_nested(task2)|
(locked) |
mutex_lock_killable_nested(task1)(locked)
Please note the
if (m1 >= m2) swap(m1. m2)
at the start.
Oleg.
H. Peter Anvin
reviewerQuoted 3 lines · Oleg Nesterov
so let me repeat, I am not arguing. But IMHO every piece of code should be understandable.
Amen!
-hpa
Signature · 2 lines
H. Peter Anvin, Intel Open Source Technology Center I work for Intel. I don't speak on their behalf.
Quoted 15 lines · Oleg Nesterov
Quoted 14 lines · Cyrill Gorcunov
Quoted 11 lines · Oleg Nesterov
Review
Quoted 7 lines · Cyrill Gorcunov
(quoted diff) +3
-2-static void access_unlock(struct task_struct *task) +static void kcmp_unlock(struct mutex *m1, struct mutex *m2) { - mutex_unlock(&task->signal->cred_guard_mutex); + if (m2 > m1) + swap(m1, m2);Well, the order doesn't matter in case of
_unlock, you can remove this part. Not that it really hurts though, I won't argue.
Quoted 4 lines
Quoted 1 line
It drops some instructions so I think it worth removing
Yes.
Final one ;) I agreed on every line of your comment, thanks a lot Oleg!
---
From: Cyrill Gorcunov <[email protected]>
Subject: syscalls, x86: Make __NR_kcmp to work with equivalent pids
In case if pid1 is equal to pid2 the kcmp will return -EBUSY, which makes no sence. Make it able to work with equivalent pids. Selftest is extended as well.
---
kernel/kcmp.c +23 -14
@@ -58,22 +58,28 @@return file; } -static void access_unlock (struct task_struct *task) +static void kcmp_unlock (struct mutex *m1,struct mutex *m2) { -mutex_unlock (&task->signal->cred_guard_mutex); +if (likely (m2 != m1)) +mutex_unlock (m2); +mutex_unlock (m1); } -static int access_trylock (struct task_struct *task) +static int kcmp_lock (struct mutex *m1,struct mutex *m2) { -if (!mutex_trylock (&task->signal->cred_guard_mutex)) -return -EBUSY ; +int err; -if (!ptrace_may_access (task,PTRACE_MODE_READ )) { -mutex_unlock (&task->signal->cred_guard_mutex); -return -EPERM ; +if (m2 > m1) +swap (m1, m2); + + err =mutex_lock_killable (m1); +if (!err &&likely (m1 != m2)) { + err =mutex_lock_killable_nested (m2,SINGLE_DEPTH_NESTING ); +if (err) +mutex_unlock (m1); } -return 0 ; +return err; } SYSCALL_DEFINE5(kcmp, pid_t, pid1, pid_t, pid2, int, type,
@@ -100,12 +106,15 @@/* * One should have enough rights to inspect task details. */ - ret =access_trylock (task1); + ret =kcmp_lock (&task1->signal->cred_guard_mutex, + &task2->signal->cred_guard_mutex);if (ret) goto err; - ret =access_trylock (task2); -if (ret) +if (!ptrace_may_access (task1,PTRACE_MODE_READ ) || + !ptrace_may_access (task2,PTRACE_MODE_READ )) { + ret = -EPERM ; goto err_unlock; + }switch (type) {case KCMP_FILE : {
@@ -149,9 +158,9 @@break ; } -access_unlock (task2); err_unlock: -access_unlock (task1); +kcmp_unlock (&task1->signal->cred_guard_mutex, + &task2->signal->cred_guard_mutex); err:put_task_struct (task1);put_task_struct (task2);
tools/testing/selftests/kcmp/kcmp_test.c +9 -0
@@ -74,6 +74,15 @@ ret =-1 ; }else printf ("PASS: 0 returned as expected\n" ); + +/* Compare with self */ + ret =sys_kcmp (pid1, pid1,KCMP_VM ,0 ,0 ); +if (ret) { +printf ("FAIL: 0 expected but %li returned\n" , ret); + ret =-1 ; + }else +printf ("PASS: 0 returned as expected\n" ); +exit (ret); }
Quoted 1 line · On Thu, 16 Feb 2012 23:29:52 +0400 Cyrill Gorcunov <[email protected]> wrote:
Final one ;)
syscalls-x86-add-__nr_kcmp-syscall-v8-fix-fix-fix-fix-fix.patch is a tie for the world record!
I folded everything back into a single patch. Here's what we currently have:
From: Cyrill Gorcunov <[email protected]>
Subject: syscalls, x86: add __NR_kcmp syscall
While doing the checkpoint-restore in the user space one need to determine whether various kernel objects (like mm_struct-s of file_struct-s) are shared between tasks and restore this state.
The 2nd step can be solved by using appropriate CLONE_ flags and the unshare syscall, while there's currently no ways for solving the 1st one.
One of the ways for checking whether two tasks share e.g. mm_struct is to provide some mm_struct ID of a task to its proc file, but showing such info considered to be not that good for security reasons.
Thus after some debates we end up in conclusion that using that named 'comparison' syscall might be the best candidate. So here is it -- __NR_kcmp.
It takes up to 5 arguments - the pids of the two tasks (which characteristics should be compared), the comparison type and (in case of comparison of files) two file descriptors.
Lookups for pids are done in the caller's PID namespace only.
At moment only x86 is supported and tested.
---
arch/x86/syscalls/syscall_32.tbl | 1 arch/x86/syscalls/syscall_64.tbl | 1 include/linux/kcmp.h | 17 + include/linux/syscalls.h | 2 kernel/Makefile | 3 kernel/kcmp.c | 186 +++++++++++++++++++++ kernel/sys_ni.c | 3 tools/testing/selftests/Makefile | 2 tools/testing/selftests/kcmp/Makefile | 29 +++ tools/testing/selftests/kcmp/kcmp_test.c | 94 ++++++++++ 10 files changed, 337 insertions(+), 1 deletion(-)
arch/x86/syscalls/syscall_32.tbl renamed from arch/x86/syscalls/syscall_32.tbl~syscalls-x86-add-__nr_kcmp-syscall-v8 +1 -0
@@ -355,3 +355,4 @@ 346 i386 setns sys_setns 347 i386 process_vm_readv sys_process_vm_readv compat_sys_process_vm_readv 348 i386 process_vm_writev sys_process_vm_writev compat_sys_process_vm_writev +349 i386 kcmp sys_kcmp
arch/x86/syscalls/syscall_64.tbl renamed from arch/x86/syscalls/syscall_64.tbl~syscalls-x86-add-__nr_kcmp-syscall-v8 +1 -0
@@ -318,3 +318,4 @@ 309 64 getcpu sys_getcpu 310 64 process_vm_readv sys_process_vm_readv 311 64 process_vm_writev sys_process_vm_writev +312 64 kcmp sys_kcmp
include/linux/kcmp.h new file +17 -0
@@ -0,0 +1,17 @@ +#ifndef _LINUX_KCMP_H +#define _LINUX_KCMP_H + +/* Comparison type */ +enum kcmp_type { +KCMP_FILE , +KCMP_VM , +KCMP_FILES , +KCMP_FS , +KCMP_SIGHAND , +KCMP_IO , +KCMP_SYSVSEM , + +KCMP_TYPES , +}; + +#endif /* _LINUX_KCMP_H */
include/linux/syscalls.h renamed from include/linux/syscalls.h~syscalls-x86-add-__nr_kcmp-syscall-v8 +2 -0
@@ -857,4 +857,6 @@ asmlinkage long sys_process_vm_writev(piunsigned long riovcnt, unsigned long flags); +asmlinkage long sys_kcmp (pid_t pid1,pid_t pid2,int type, +unsigned long idx1,unsigned long idx2);#endif
kernel/Makefile renamed from kernel/Makefile~syscalls-x86-add-__nr_kcmp-syscall-v8 +3 -0
@@ -25,6 +25,9 @@ endif obj-y += sched/ obj-y += power/ +ifeq ($(CONFIG_CHECKPOINT_RESTORE),y) +obj-$(CONFIG_X86) += kcmp.o +endif obj-$(CONFIG_FREEZER) += freezer.o obj-$(CONFIG_PROFILING) += profile.o obj-$(CONFIG_STACKTRACE) += stacktrace.o
kernel/kcmp.c new file +186 -0
186 lines not on this page · raw message
kernel/sys_ni.c renamed from kernel/sys_ni.c~syscalls-x86-add-__nr_kcmp-syscall-v8 +3 -0
@@ -203,3 +203,6 @@ cond_syscall(sys_fanotify_mark);cond_syscall (sys_name_to_handle_at);cond_syscall (sys_open_by_handle_at);cond_syscall (compat_sys_open_by_handle_at); + +/* compare kernel pointers */ +cond_syscall (sys_kcmp);
tools/testing/selftests/kcmp/Makefile new file +29 -0
@@ -0,0 +1,29 @@ +uname_M := $(shell uname -m 2>/dev/null || echo not) +ARCH ?= $(shell echo $(uname_M) | sed -e s/i.86/i386/) +ifeq ($(ARCH),i386) + ARCH := X86 + CFLAGS := -DCONFIG_X86_32 -D__i386__ +endif +ifeq ($(ARCH),x86_64) + ARCH := X86 + CFLAGS := -DCONFIG_X86_64 -D__x86_64__ +endif + +CFLAGS += -I../../../../arch/x86/include/generated/ +CFLAGS += -I../../../../include/ +CFLAGS += -I../../../../usr/include/ +CFLAGS += -I../../../../arch/x86/include/ + +all: +ifeq ($(ARCH),X86) + gcc $(CFLAGS) kcmp_test.c -o run_test +else + echo "Not an x86 target, can't build kcmp selftest" +endif + +run-tests: all + ./kcmp_test + +clean: + rm -fr ./run_test + rm -fr ./test-file
tools/testing/selftests/kcmp/kcmp_test.c new file +94 -0
94 lines not on this page · raw message
tools/testing/selftests/Makefile renamed from tools/testing/selftests/Makefile~syscalls-x86-add-__nr_kcmp-syscall-v8 +1 -1
@@ -1,4 +1,4 @@ -TARGETS = breakpoints vm +TARGETS = breakpoints vm kcmp all: for TARGET in $(TARGETS); do \
_
Quoted 7 lines · Andrew Morton
Quoted 1 line · On Thu, 16 Feb 2012 23:29:52 +0400 Cyrill Gorcunov <[email protected]> wrote:
Final one ;)
syscalls-x86-add-__nr_kcmp-syscall-v8-fix-fix-fix-fix-fix.patch is a tie for the world record!
It was close to v13 ;)
Quoted 4 lines
I folded everything back into a single patch. Here's what we currently have:
Thanks a lot, Andrew!
Cyrill
Back on to kcmp.
Quoted 19 lines · On Wed, 15 Feb 2012 20:27:52 +0400 Cyrill Gorcunov <[email protected]> wrote:
Quoted 17 lines · Oleg Nesterov
Not a comment, but the question. I am just curious...
Review
Quoted 12 lines
(quoted diff) +12
-0+/* + * We don't expose real in-memory order of objects for security + * reasons, still the comparison results should be suitable for + * sorting. Thus, we obfuscate kernel pointers values and compare + * the production instead. + */ +static unsigned long cookies[KCMP_TYPES][2] __read_mostly; + +static long kptr_obfuscate(long v, int type) +{ + return (v ^ cookies[type][0]) * cookies[type][1]; +}OK, but why do we need this per type? Just to add more obfuscation or there is another reason?
Quoted 1 line
Just to add more obfuscation.
Having re-read most of the (enormous) email discussion on the kcmp() syscall patch, I'm thinking:
- Nobody seems to understand the obfuscation logic. Jon sounded confused, Oleg sounds confused and it's rather unclear what it does, how it does it and why it does it.
- Lots of people have looked at the code and made comments and there have been lots of changes. But we presently have zero Acked-by's and Reviewed-by's.
I guess this means that at present nobody is aware of any issues with the proposal, btu nobody is terribly excisted about it either?
So what do people think? Any issues? Any nacks? Should I sneak it into Linus this week or do we need to go another round with it all?
I'd like to at least have a fighting chance of understnading what's going on with that obfuscation code.
From: Cyrill Gorcunov <[email protected]>
Subject: syscalls, x86: add __NR_kcmp syscall
While doing the checkpoint-restore in the user space one need to determine whether various kernel objects (like mm_struct-s of file_struct-s) are shared between tasks and restore this state.
The 2nd step can be solved by using appropriate CLONE_ flags and the unshare syscall, while there's currently no ways for solving the 1st one.
One of the ways for checking whether two tasks share e.g. mm_struct is to provide some mm_struct ID of a task to its proc file, but showing such info considered to be not that good for security reasons.
Thus after some debates we end up in conclusion that using that named 'comparison' syscall might be the best candidate. So here is it -- __NR_kcmp.
It takes up to 5 arguments - the pids of the two tasks (which characteristics should be compared), the comparison type and (in case of comparison of files) two file descriptors.
Lookups for pids are done in the caller's PID namespace only.
At moment only x86 is supported and tested.
---
arch/x86/syscalls/syscall_32.tbl | 1 arch/x86/syscalls/syscall_64.tbl | 1 include/linux/kcmp.h | 17 + include/linux/syscalls.h | 2 kernel/Makefile | 3 kernel/kcmp.c | 187 +++++++++++++++++++++ kernel/sys_ni.c | 3 tools/testing/selftests/Makefile | 2 tools/testing/selftests/kcmp/Makefile | 29 +++ tools/testing/selftests/kcmp/kcmp_test.c | 94 ++++++++++ 10 files changed, 338 insertions(+), 1 deletion(-)
arch/x86/syscalls/syscall_32.tbl renamed from arch/x86/syscalls/syscall_32.tbl~syscalls-x86-add-__nr_kcmp-syscall-v8 +1 -0
@@ -355,3 +355,4 @@ 346 i386 setns sys_setns 347 i386 process_vm_readv sys_process_vm_readv compat_sys_process_vm_readv 348 i386 process_vm_writev sys_process_vm_writev compat_sys_process_vm_writev +349 i386 kcmp sys_kcmp
arch/x86/syscalls/syscall_64.tbl renamed from arch/x86/syscalls/syscall_64.tbl~syscalls-x86-add-__nr_kcmp-syscall-v8 +1 -0
@@ -318,6 +318,7 @@ 309 common getcpu sys_getcpu 310 64 process_vm_readv sys_process_vm_readv 311 64 process_vm_writev sys_process_vm_writev +312 64 kcmp sys_kcmp # # x32-specific system call numbers start at 512 to avoid cache impact # for native 64-bit operation.
include/linux/kcmp.h new file +17 -0
@@ -0,0 +1,17 @@ +#ifndef _LINUX_KCMP_H +#define _LINUX_KCMP_H + +/* Comparison type */ +enum kcmp_type { +KCMP_FILE , +KCMP_VM , +KCMP_FILES , +KCMP_FS , +KCMP_SIGHAND , +KCMP_IO , +KCMP_SYSVSEM , + +KCMP_TYPES , +}; + +#endif /* _LINUX_KCMP_H */
include/linux/syscalls.h renamed from include/linux/syscalls.h~syscalls-x86-add-__nr_kcmp-syscall-v8 +2 -0
@@ -858,4 +858,6 @@ asmlinkage long sys_process_vm_writev(piunsigned long riovcnt, unsigned long flags); +asmlinkage long sys_kcmp (pid_t pid1,pid_t pid2,int type, +unsigned long idx1,unsigned long idx2);#endif
kernel/Makefile renamed from kernel/Makefile~syscalls-x86-add-__nr_kcmp-syscall-v8 +3 -0
@@ -25,6 +25,9 @@ endif obj-y += sched/ obj-y += power/ +ifeq ($(CONFIG_CHECKPOINT_RESTORE),y) +obj-$(CONFIG_X86) += kcmp.o +endif obj-$(CONFIG_FREEZER) += freezer.o obj-$(CONFIG_PROFILING) += profile.o obj-$(CONFIG_STACKTRACE) += stacktrace.o
kernel/kcmp.c new file +187 -0
187 lines not on this page · raw message
kernel/sys_ni.c renamed from kernel/sys_ni.c~syscalls-x86-add-__nr_kcmp-syscall-v8 +3 -0
@@ -203,3 +203,6 @@ cond_syscall(sys_fanotify_mark);cond_syscall (sys_name_to_handle_at);cond_syscall (sys_open_by_handle_at);cond_syscall (compat_sys_open_by_handle_at); + +/* compare kernel pointers */ +cond_syscall (sys_kcmp);
tools/testing/selftests/Makefile renamed from tools/testing/selftests/Makefile~syscalls-x86-add-__nr_kcmp-syscall-v8 +1 -1
@@ -1,4 +1,4 @@ -TARGETS = breakpoints vm +TARGETS = breakpoints vm kcmp all: for TARGET in $(TARGETS); do \
tools/testing/selftests/kcmp/Makefile new file +29 -0
@@ -0,0 +1,29 @@ +uname_M := $(shell uname -m 2>/dev/null || echo not) +ARCH ?= $(shell echo $(uname_M) | sed -e s/i.86/i386/) +ifeq ($(ARCH),i386) + ARCH := X86 + CFLAGS := -DCONFIG_X86_32 -D__i386__ +endif +ifeq ($(ARCH),x86_64) + ARCH := X86 + CFLAGS := -DCONFIG_X86_64 -D__x86_64__ +endif + +CFLAGS += -I../../../../arch/x86/include/generated/ +CFLAGS += -I../../../../include/ +CFLAGS += -I../../../../usr/include/ +CFLAGS += -I../../../../arch/x86/include/ + +all: +ifeq ($(ARCH),X86) + gcc $(CFLAGS) kcmp_test.c -o run_test +else + echo "Not an x86 target, can't build kcmp selftest" +endif + +run-tests: all + ./kcmp_test + +clean: + rm -fr ./run_test + rm -fr ./test-file
tools/testing/selftests/kcmp/kcmp_test.c new file +94 -0
94 lines not on this page · raw message
_
Quoted 24 lines · Andrew Morton
Back on to kcmp.
Quoted 19 lines · On Wed, 15 Feb 2012 20:27:52 +0400 Cyrill Gorcunov <[email protected]> wrote:
Quoted 17 lines · Oleg Nesterov
Not a comment, but the question. I am just curious...
kernel/kcmp.c line 27
Quoted 12 lines
kernel/kcmp.c new file +12
-0+/* +* We don't expose real in-memory order of objects for security +* reasons, still the comparison results should be suitable for +* sorting. Thus, we obfuscate kernel pointers values and compare +* the production instead. +*/ +static unsigned long cookies[KCMP_TYPES ][2 ]__read_mostly ; + +static long kptr_obfuscate (long v,int type) +{ +return (v ^ cookies[type][0 ]) * cookies[type][1 ]; +}OK, but why do we need this per type? Just to add more obfuscation or there is another reason?
Quoted 8 lines
Quoted 1 line
Just to add more obfuscation.
Having re-read most of the (enormous) email discussion on the
kcmp()syscall patch, I'm thinking:- Nobody seems to understand the obfuscation logic. Jon sounded confused, Oleg sounds confused and it's rather unclear what it does, how it does it and why it does it.
The obfuscation logic was done with great help from hpa@. And the main idea was to have ordered results after obfuscation. Per-type noise increase randomization of results. So Andrew, I actually dont know what to add here. We don't want to provide kernel order back to user-space in naked manner.
Quoted 5 lines
- Lots of people have looked at the code and made comments and there have been lots of changes. But we presently have zero Acked-by's and Reviewed-by's.
I guess I can ask hpa@ and Eric for Reviewed-by or Acked-by tag?
Quoted 3 lines
I guess this means that at present nobody is aware of any issues with the proposal, btu nobody is terribly excisted about it either?
I would rather say not much people yet use it.
Quoted 5 lines
So what do people think? Any issues? Any nacks? Should I sneak it into Linus this week or do we need to go another round with it all?
I'd like to at least have a fighting chance of understnading what's going on with that obfuscation code.
Cyrill
H. Peter Anvin
reviewerQuoted 8 lines · Cyrill Gorcunov
Quoted 7 lines
Having re-read most of the (enormous) email discussion on the
kcmp()syscall patch, I'm thinking:- Nobody seems to understand the obfuscation logic. Jon sounded confused, Oleg sounds confused and it's rather unclear what it does, how it does it and why it does it.
Quoted 6 lines
The obfuscation logic was done with great help from hpa@. And the main idea was to have ordered results after obfuscation. Per-type noise increase randomization of results. So Andrew, I actually dont know what to add here. We don't want to provide kernel order back to user-space in naked manner.
The obfuscation logic is to provide a 1:1 mapping but which doesn't preserve ordering, thereby avoid leaking information of kernel pointers to user space.
-hpa
Eric W. Biederman
Acked-byQuoted 24 lines · Andrew Morton
Back on to kcmp.
Quoted 19 lines · On Wed, 15 Feb 2012 20:27:52 +0400 Cyrill Gorcunov <[email protected]> wrote:
Quoted 17 lines · Oleg Nesterov
Not a comment, but the question. I am just curious...
kernel/kcmp.c line 27
Quoted 12 lines
kernel/kcmp.c new file +12
-0+/* +* We don't expose real in-memory order of objects for security +* reasons, still the comparison results should be suitable for +* sorting. Thus, we obfuscate kernel pointers values and compare +* the production instead. +*/ +static unsigned long cookies[KCMP_TYPES ][2 ]__read_mostly ; + +static long kptr_obfuscate (long v,int type) +{ +return (v ^ cookies[type][0 ]) * cookies[type][1 ]; +}OK, but why do we need this per type? Just to add more obfuscation or there is another reason?
Quoted 8 lines
Quoted 1 line
Just to add more obfuscation.
Having re-read most of the (enormous) email discussion on the
kcmp()syscall patch, I'm thinking:- Nobody seems to understand the obfuscation logic. Jon sounded confused, Oleg sounds confused and it's rather unclear what it does, how it does it and why it does it.
Peter explained it fairly well earlier. The xor trivially makes sense to me. I don't recall what the multiplication does.
It would be nice if someone would get Peter's comment on why the multiply into a comment. But obscuring things makes sense.
Quoted 6 lines
- Lots of people have looked at the code and made comments and there have been lots of changes. But we presently have zero Acked-by's and Reviewed-by's.
I guess this means that at present nobody is aware of any issues with the proposal, btu nobody is terribly excisted about it either?
Having just read through it again the only possible issue I can see is that we compare file descriptors after dropping all of the locks.
Since rcu_read_lock doesn't participate in ABBA deadlocks. My gut feel is that we should hold rcu_read_lock across the hole file pointer comparison to remove the possibility of races as file descriptor pointers go away.
Still in practice I don't think it matters. At worst there is the slightest possibility of returning a value instead of -EBADF. The expectation is for all of the tasks we are operating on to be frozen, and even if the tasks are not frozen it is a very tiny window for a race to be in.
Quoted 2 lines
So what do people think? Any issues? Any nacks? Should I sneak it into Linus this week or do we need to go another round with it all?
Quoted 2 lines
I'd like to at least have a fighting chance of understnading what's going on with that obfuscation code.
My gut feel is that this code is good enough. Any and all security issues have been addressed. Having the system call would seem to add momentum to the people working on checkpoint/restart. So I don't see why this patch should not go forward, unless someone can point out an outright bug.
Eric
Quoted 467 lines
From: Cyrill Gorcunov <[email protected]>
Subject: syscalls, x86: add__NR_kcmpsyscallWhile doing the checkpoint-restore in the user space one need to determine whether various kernel objects (like mm_struct-s of file_struct-s) are shared between tasks and restore this state.
The 2nd step can be solved by using appropriate
CLONE_flags and the unshare syscall, while there's currently no ways for solving the 1st one.One of the ways for checking whether two tasks share e.g.
mm_structis to provide somemm_structID of a task to its proc file, but showing such info considered to be not that good for security reasons.Thus after some debates we end up in conclusion that using that named 'comparison' syscall might be the best candidate. So here is it --
__NR_kcmp.It takes up to 5 arguments - the pids of the two tasks (which characteristics should be compared), the comparison type and (in case of comparison of files) two file descriptors.
Lookups for pids are done in the caller's PID namespace only.
At moment only x86 is supported and tested.
[[email protected]: fix up selftests, warnings][[email protected]: include errno.h]Signed-off-by Cyrill Gorcunov <[email protected]>Cc "Eric W. Biederman" <[email protected]>Cc Pavel Emelyanov <[email protected]>Cc Andrey Vagin <[email protected]>Cc KOSAKI Motohiro <[email protected]>Cc Ingo Molnar <[email protected]>Cc H. Peter Anvin <[email protected]>Cc Thomas Gleixner <[email protected]>Cc Glauber Costa <[email protected]>Cc Andi Kleen <[email protected]>Cc Tejun Heo <[email protected]>Cc Matt Helsley <[email protected]>Cc Pekka Enberg <[email protected]>Cc Eric Dumazet <[email protected]>Cc Vasiliy Kulikov <[email protected]>Cc Alexey Dobriyan <[email protected]>Cc Michal Marek <[email protected]>Cc Frederic Weisbecker <[email protected]>Signed-off-by Andrew Morton <[email protected]>---
arch/x86/syscalls/syscall_32.tbl | 1 arch/x86/syscalls/syscall_64.tbl | 1 include/linux/kcmp.h | 17 + include/linux/syscalls.h | 2 kernel/Makefile | 3 kernel/kcmp.c | 187 +++++++++++++++++++++ kernel/sys_ni.c | 3 tools/testing/selftests/Makefile | 2 tools/testing/selftests/kcmp/Makefile | 29 +++ tools/testing/selftests/kcmp/kcmp_test.c | 94 ++++++++++ 10 files changed, 338 insertions(+), 1 deletion(-)diff -puN
arch/x86/syscalls/syscall_32.tbl~syscalls-x86-add-__nr_kcmp-syscall-v8arch/x86/syscalls/syscall_32.tblarch/x86/syscalls/syscall_32.tbl renamed from arch/x86/syscalls/syscall_32.tbl~syscalls-x86-add-__nr_kcmp-syscall-v8 +1
-0@@ -355,3 +355,4 @@ 346 i386 setns sys_setns 347 i386 process_vm_readv sys_process_vm_readv compat_sys_process_vm_readv 348 i386 process_vm_writev sys_process_vm_writev compat_sys_process_vm_writev +349 i386 kcmp sys_kcmpdiff -puN
arch/x86/syscalls/syscall_64.tbl~syscalls-x86-add-__nr_kcmp-syscall-v8arch/x86/syscalls/syscall_64.tblarch/x86/syscalls/syscall_64.tbl renamed from arch/x86/syscalls/syscall_64.tbl~syscalls-x86-add-__nr_kcmp-syscall-v8 +1
-0@@ -318,6 +318,7 @@ 309 common getcpu sys_getcpu 310 64 process_vm_readv sys_process_vm_readv 311 64 process_vm_writev sys_process_vm_writev +312 64 kcmp sys_kcmp # # x32-specific system call numbers start at 512 to avoid cache impact # for native 64-bit operation.diff -puN
/dev/nullinclude/linux/kcmp.hinclude/linux/kcmp.h new file +17
-0@@ -0,0 +1,17 @@ +#ifndef _LINUX_KCMP_H +#define _LINUX_KCMP_H + +/* Comparison type */ +enum kcmp_type { +KCMP_FILE , +KCMP_VM , +KCMP_FILES , +KCMP_FS , +KCMP_SIGHAND , +KCMP_IO , +KCMP_SYSVSEM , + +KCMP_TYPES , +}; + +#endif /* _LINUX_KCMP_H */ diff -puN
include/linux/syscalls.h~syscalls-x86-add-__nr_kcmp-syscall-v8include/linux/syscalls.hinclude/linux/syscalls.h renamed from include/linux/syscalls.h~syscalls-x86-add-__nr_kcmp-syscall-v8 +2
-0@@ -858,4 +858,6 @@ asmlinkage long sys_process_vm_writev(piunsigned long riovcnt, unsigned long flags); +asmlinkage long sys_kcmp (pid_t pid1,pid_t pid2,int type, +unsigned long idx1,unsigned long idx2);#endif diff -puN
kernel/Makefile~syscalls-x86-add-__nr_kcmp-syscall-v8kernel/Makefilekernel/Makefile renamed from kernel/Makefile~syscalls-x86-add-__nr_kcmp-syscall-v8 +3
-0@@ -25,6 +25,9 @@ endif obj-y += sched/ obj-y += power/ +ifeq ($(CONFIG_CHECKPOINT_RESTORE),y) +obj-$(CONFIG_X86) += kcmp.o +endif obj-$(CONFIG_FREEZER) += freezer.o obj-$(CONFIG_PROFILING) += profile.o obj-$(CONFIG_STACKTRACE) += stacktrace.odiff -puN
/dev/nullkernel/kcmp.ckernel/kcmp.c new file +187
-0@@ -0,0 +1,187 @@ +#include <linux/kernel.h> +#include <linux/syscalls.h> +#include <linux/fdtable.h> +#include <linux/string.h> +#include <linux/random.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/cache.h> +#include <linux/bug.h> +#include <linux/err.h> +#include <linux/kcmp.h> + +#include <asm/unistd.h> + +/* +* We don't expose real in-memory order of objects for security +* reasons, still the comparison results should be suitable for +* sorting. Thus, we obfuscate kernel pointers values and compare +* the production instead. +*/ +static unsigned long cookies[KCMP_TYPES ][2 ]__read_mostly ; + +static long kptr_obfuscate (long v,int type) +{ +return (v ^ cookies[type][0 ]) * cookies[type][1 ]; +} + +/* +* 0 - equal, i.e. v1 = v2 +* 1 - less than, i.e. v1 < v2 +* 2 - greater than, i.e. v1 > v2 +* 3 - not equal but ordering unavailable (reserved for future) +*/ +static int kcmp_ptr (void *v1,void *v2,enum kcmp_type type) +{ +long ret; + + ret =kptr_obfuscate ((long )v1, type) -kptr_obfuscate ((long )v2, type); + +return (ret <0 ) | ((ret >0 ) <<1 ); +} + +/* The caller must have pinned the task */ +static struct file * +get_file_raw_ptr (struct task_struct *task,unsigned int idx) +{ +struct file *file =NULL ; + +task_lock (task); +rcu_read_lock (); + +if (task->files) + file =fcheck_files (task->files, idx); + +rcu_read_unlock (); +task_unlock (task); + +return file; +} + +static void kcmp_unlock (struct mutex *m1,struct mutex *m2) +{ +if (likely (m2 != m1)) +mutex_unlock (m2); +mutex_unlock (m1); +} + +static int kcmp_lock (struct mutex *m1,struct mutex *m2) +{ +int err; + +if (m2 > m1) +swap (m1, m2); + + err =mutex_lock_killable (m1); +if (!err &&likely (m1 != m2)) { + err =mutex_lock_killable_nested (m2,SINGLE_DEPTH_NESTING ); +if (err) +mutex_unlock (m1); + } + +return err; +} + +SYSCALL_DEFINE5 (kcmp, pid_t, pid1, pid_t, pid2, int, type, + unsigned long, idx1, unsigned long, idx2) +{ +struct task_struct *task1, *task2; +int ret; + +rcu_read_lock (); + +/* +* Tasks are looked up in caller's PID namespace only. +*/ + task1 =find_task_by_vpid (pid1); + task2 =find_task_by_vpid (pid2); +if (!task1 || !task2) + goto err_no_task; + +get_task_struct (task1); +get_task_struct (task2); + +rcu_read_unlock (); + +/* +* One should have enough rights to inspect task details. +*/ + ret =kcmp_lock (&task1->signal->cred_guard_mutex, + &task2->signal->cred_guard_mutex); +if (ret) + goto err; +if (!ptrace_may_access (task1,PTRACE_MODE_READ ) || + !ptrace_may_access (task2,PTRACE_MODE_READ )) { + ret = -EPERM ; + goto err_unlock; + } + +switch (type) { +case KCMP_FILE : { +struct file *filp1, *filp2; + + filp1 =get_file_raw_ptr (task1, idx1); + filp2 =get_file_raw_ptr (task2, idx2); + +if (filp1 && filp2) + ret =kcmp_ptr (filp1, filp2,KCMP_FILE ); +else + ret = -EBADF ; +break ; + } +case KCMP_VM : + ret =kcmp_ptr (task1->mm, task2->mm,KCMP_VM ); +break ; +case KCMP_FILES : + ret =kcmp_ptr (task1->files, task2->files,KCMP_FILES ); +break ; +case KCMP_FS : + ret =kcmp_ptr (task1->fs, task2->fs,KCMP_FS ); +break ; +case KCMP_SIGHAND : + ret =kcmp_ptr (task1->sighand, task2->sighand,KCMP_SIGHAND ); +break ; +case KCMP_IO : + ret =kcmp_ptr (task1->io_context, task2->io_context,KCMP_IO ); +break ; +case KCMP_SYSVSEM : +#ifdef CONFIG_SYSVIPC + ret =kcmp_ptr (task1->sysvsem.undo_list, + task2->sysvsem.undo_list, +KCMP_SYSVSEM ); +#else + ret = -EOPNOTSUPP ; +#endif +break ; +default : + ret = -EINVAL ; +break ; + } + +err_unlock: +kcmp_unlock (&task1->signal->cred_guard_mutex, + &task2->signal->cred_guard_mutex); +err: +put_task_struct (task1); +put_task_struct (task2); + +return ret; + +err_no_task: +rcu_read_unlock (); +return -ESRCH ; +} + +static __init int kcmp_cookies_init (void ) +{ +int i; + +get_random_bytes (cookies,sizeof (cookies)); + +for (i =0 ; i <KCMP_TYPES ; i++) + cookies[i][1 ] |= (~(~0UL >>1 ) |1 ); + +return 0 ; +} +arch_initcall (kcmp_cookies_init);diff -puN
kernel/sys_ni.c~syscalls-x86-add-__nr_kcmp-syscall-v8kernel/sys_ni.ckernel/sys_ni.c renamed from kernel/sys_ni.c~syscalls-x86-add-__nr_kcmp-syscall-v8 +3
-0@@ -203,3 +203,6 @@ cond_syscall(sys_fanotify_mark);cond_syscall (sys_name_to_handle_at);cond_syscall (sys_open_by_handle_at);cond_syscall (compat_sys_open_by_handle_at); + +/* compare kernel pointers */ +cond_syscall (sys_kcmp);diff -puN
tools/testing/selftests/Makefile~syscalls-x86-add-__nr_kcmp-syscall-v8tools/testing/selftests/Makefiletools/testing/selftests/Makefile renamed from tools/testing/selftests/Makefile~syscalls-x86-add-__nr_kcmp-syscall-v8 +1
-1@@ -1,4 +1,4 @@ -TARGETS = breakpoints vm +TARGETS = breakpoints vm kcmp all: for TARGET in $(TARGETS); do \diff -puN
/dev/nulltools/testing/selftests/kcmp/Makefiletools/testing/selftests/kcmp/Makefile new file +29
-0@@ -0,0 +1,29 @@ +uname_M := $(shell uname -m 2>/dev/null || echo not) +ARCH ?= $(shell echo $(uname_M) | sed -e s/i.86/i386/) +ifeq ($(ARCH),i386) + ARCH := X86 + CFLAGS := -DCONFIG_X86_32 -D__i386__ +endif +ifeq ($(ARCH),x86_64) + ARCH := X86 + CFLAGS := -DCONFIG_X86_64 -D__x86_64__ +endif + +CFLAGS += -I../../../../arch/x86/include/generated/ +CFLAGS += -I../../../../include/ +CFLAGS += -I../../../../usr/include/ +CFLAGS += -I../../../../arch/x86/include/ + +all: +ifeq ($(ARCH),X86) + gcc $(CFLAGS) kcmp_test.c -o run_test +else + echo "Not an x86 target, can't build kcmp selftest" +endif + +run-tests: all + ./kcmp_test + +clean: + rm -fr ./run_test + rm -fr ./test-filediff -puN
/dev/nulltools/testing/selftests/kcmp/kcmp_test.ctools/testing/selftests/kcmp/kcmp_test.c new file +94
-0@@ -0,0 +1,94 @@ +#define _GNU_SOURCE + +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <limits.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <fcntl.h> + +#include <linux/unistd.h> +#include <linux/kcmp.h> + +#include <sys/syscall.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> + +static long sys_kcmp (int pid1,int pid2,int type,int fd1,int fd2) +{ +return syscall (__NR_kcmp, pid1, pid2, type, fd1, fd2); +} + +int main (int argc,char **argv) +{ +const char kpath[] ="kcmp-test-file" ; +int pid1, pid2; +int fd1, fd2; +int status; + + fd1 =open (kpath,O_RDWR |O_CREAT |O_TRUNC ,0644 ); + pid1 =getpid (); + +if (fd1 <0 ) { +perror ("Can't create file" ); +exit (1 ); + } + + pid2 =fork (); +if (pid2 <0 ) { +perror ("fork failed" ); +exit (1 ); + } + +if (!pid2) { +int pid2 =getpid (); +int ret; + + fd2 =open (kpath,O_RDWR ,0644 ); +if (fd2 <0 ) { +perror ("Can't open file" ); +exit (1 ); + } + +/* An example of output and arguments */ +printf ("pid1: %6d pid2: %6d FD: %2ld FILES: %2ld VM: %2ld " +"FS: %2ld SIGHAND: %2ld IO: %2ld SYSVSEM: %2ld " +"INV: %2ld\n" , + pid1, pid2, +sys_kcmp (pid1, pid2,KCMP_FILE , fd1, fd2), +sys_kcmp (pid1, pid2,KCMP_FILES ,0 ,0 ), +sys_kcmp (pid1, pid2,KCMP_VM ,0 ,0 ), +sys_kcmp (pid1, pid2,KCMP_FS ,0 ,0 ), +sys_kcmp (pid1, pid2,KCMP_SIGHAND ,0 ,0 ), +sys_kcmp (pid1, pid2,KCMP_IO ,0 ,0 ), +sys_kcmp (pid1, pid2,KCMP_SYSVSEM ,0 ,0 ), + +/* This one should fail */ +sys_kcmp (pid1, pid2,KCMP_TYPES +1 ,0 ,0 )); + +/* This one should return same fd */ + ret =sys_kcmp (pid1, pid2,KCMP_FILE , fd1, fd1); +if (ret) { +printf ("FAIL: 0 expected but %d returned\n" , ret); + ret =-1 ; + }else +printf ("PASS: 0 returned as expected\n" ); + +/* Compare with self */ + ret =sys_kcmp (pid1, pid1,KCMP_VM ,0 ,0 ); +if (ret) { +printf ("FAIL: 0 expected but %li returned\n" , ret); + ret =-1 ; + }else +printf ("PASS: 0 returned as expected\n" ); + +exit (ret); + } + +waitpid (pid2, &status,P_ALL ); + +return 0 ; +}_
Quoted 11 lines · H. Peter Anvin
Quoted 6 lines
The obfuscation logic was done with great help from hpa@. And the main idea was to have ordered results after obfuscation. Per-type noise increase randomization of results. So Andrew, I actually dont know what to add here. We don't want to provide kernel order back to user-space in naked manner.
The obfuscation logic is to provide a 1:1 mapping but which doesn't preserve ordering, thereby avoid leaking information of kernel pointers to user space.
OK, Peter, would the following comment bring light on the obfuscation procedure?
---
Add a comment on kcmp obfuscation method
--- kernel/kcmp.c | 11 +++++++++++ 1 file changed, 11 insertions(+)
kernel/kcmp.c +11 -0
@@ -17,6 +17,17 @@* reasons, still the comparison results should be suitable for * sorting. Thus, we obfuscate kernel pointers values and compare * the production instead. +* +* The obfuscation is done in two steps. First -- we use xor on +* kernel pointer with random value, which puts pointer into +* a new position in reordered space. Second -- we multiply +* the xor production with big odd random number to permute +* bits even more (the oddity is important here, it allow +* us to have meaningful production even if multiplicants +* are big numbers). +* +* Note also the obfuscation itself is invisible to user-space +* and if needed it can be changed to any suitable scheme. */ static unsigned long cookies[KCMP_TYPES ][2 ]__read_mostly ;
H. Peter Anvin
reviewerQuoted 3 lines · Cyrill Gorcunov
+ * bits even more (the oddity is important here, it allow
+ * us to have meaningful production even if multiplicants
+ * are big numbers).
I would be more clear:
"the odd multiplier guarantees that the product is unique ever after the high bits are truncated, since any odd number is relative prime to 2^n".
-hpa
Quoted 10 lines · H. Peter Anvin
Quoted 3 lines · Cyrill Gorcunov
+ * bits even more (the oddity is important here, it allow
+ * us to have meaningful production even if multiplicants
+ * are big numbers).I would be more clear:
"the odd multiplier guarantees that the product is unique ever after the high bits are truncated, since any odd number is relative prime to 2^n".
Yeah, updated, thanks. Andrew, fold it up please.
---
Subject: Add a comment on kcmp obfuscation method
--- kernel/kcmp.c | 11 +++++++++++ 1 file changed, 11 insertions(+)
kernel/kcmp.c +11 -0
@@ -17,6 +17,17 @@* reasons, still the comparison results should be suitable for * sorting. Thus, we obfuscate kernel pointers values and compare * the production instead. +* +* The obfuscation is done in two steps. First -- we use xor on +* kernel pointer with random value, which puts pointer into +* a new position in reordered space. Second -- we multiply +* the xor production with big odd random number to permute +* bits even more (the odd multiplier guarantees that the product +* is unique ever after the high bits are truncated, since any odd +* number is relative prime to 2^n). +* +* Note also the obfuscation itself is invisible to user-space +* and if needed it can be changed to any suitable scheme. */ static unsigned long cookies[KCMP_TYPES ][2 ]__read_mostly ;
On Mon, Apr 09, 2012 at 08:25:22PM -0700, Eric W. Biederman wrote:
...
Quoted 9 lines
Having just read through it again the only possible issue I can see is that we compare file descriptors after dropping all of the locks.
Since
rcu_read_lockdoesn't participate in ABBA deadlocks. My gut feel is that we should holdrcu_read_lockacross the hole file pointer comparison to remove the possibility of races as file descriptor pointers go away.
Quoted 5 lines
Still in practice I don't think it matters. At worst there is the slightest possibility of returning a value instead of
-EBADF. The expectation is for all of the tasks we are operating on to be frozen, and even if the tasks are not frozen it is a very tiny window for a race to be in.
yeah, we use this call heavily on stopped tasks atm
Quoted 5 lines
Quoted 2 lines
So what do people think? Any issues? Any nacks? Should I sneak it into Linus this week or do we need to go another round with it all?
Thanks a lot, Eric!
Cyrill
Quoted 10 lines · Cyrill Gorcunov
kernel/kcmp.c +3
-0@@ -17,6 +17,17 @@* reasons, still the comparison results should be suitable for * sorting. Thus, we obfuscate kernel pointers values and compare * the production instead. +* +* The obfuscation is done in two steps. First -- we use xor on +* kernel pointer with random value, which puts pointer into
kernel/kcmp.c line 32
Quoted 10 lines
kernel/kcmp.c +8
-0+* a new position in reordered space. Second -- we multiply +* the xor production with big odd random number to permute +* bits even more (the oddity is important here, it allow +* us to have meaningful production even if multiplicants +* are big numbers). +* +* Note also the obfuscation itself is invisible to user-space +* and if needed it can be changed to any suitable scheme. */ static unsigned long cookies[KCMP_TYPES ][2 ]__read_mostly ;
OK, since this is discussed again...
Can this comment can also explain why do we obfuscate the pointers by type? I mean, I don't really understand why the one-dimensional cookies[2] is "not enough" from security pov.
Oleg.
H. Peter Anvin
reviewerQuoted 7 lines · Oleg Nesterov
OK, since this is discussed again...
Can this comment can also explain why do we obfuscate the pointers by type? I mean, I don't really understand why the one-dimensional cookies[2] is "not enough" from security pov.
Because it's cheap. "Just enough" is not what you want to shoot for, ever, you want to get past the "just enough" point and then consider "what can I get for cheap at this point"?
-hpa
Quoted 5 lines · H. Peter Anvin
Quoted 3 lines · Oleg Nesterov
OK, since this is discussed again...
Quoted 7 lines
Quoted 3 lines
Can this comment can also explain why do we obfuscate the pointers by type? I mean, I don't really understand why the one-dimensional cookies[2] is "not enough" from security pov.
Because it's cheap. "Just enough" is not what you want to shoot for, ever, you want to get past the "just enough" point and then consider "what can I get for cheap at this point"?
OK, I am not arguing. Just I thought that the small note like "we are doing this per-type to obfuscate even more" can help. I wouldn't have asked, but Cyrill rewrites this comment anyway.
Perhaps this is just me, but my first (and wrong) impression was that somehow this is needed for correctness.
Oleg.
Quoted 1 line · Andrew Morton
Back on to kcmp.
I've totally forgotten what my original comment(s) on this patch were.. :)
kernel/kcmp.c line 39
Quoted 11 lines
kernel/kcmp.c new file +11
-0+/* +* 0 - equal, i.e. v1 = v2 +* 1 - less than, i.e. v1 < v2 +* 2 - greater than, i.e. v1 > v2 +* 3 - not equal but ordering unavailable (reserved for future) +*/ +static int kcmp_ptr (void *v1,void *v2,enum kcmp_type type) +{ +long ret; + + ret =kptr_obfuscate ((long )v1, type) -kptr_obfuscate ((long )v2, type);
I'm not able to convince myself that "less than" and "greater than" mean anything - do we have a good proof that for all v1 and v2, the obfuscated pointers have the same ordering as the original pointers?
Hmm... consider the simplified example v1 = 5 and v2= 16., and cookies[0] is also 16. Then obfus(v1) == 21, and obfus(v2) == 0, and the ordering is different. So I'm thinking 0 and 3 are the only sane return values?
Or do I need more caffeine?
Quoted 16 lines · H. Peter Anvin
Quoted 8 lines · Cyrill Gorcunov
Quoted 7 lines
Having re-read most of the (enormous) email discussion on the
kcmp()syscall patch, I'm thinking:- Nobody seems to understand the obfuscation logic. Jon sounded confused, Oleg sounds confused and it's rather unclear what it does, how it does it and why it does it.
Quoted 6 lines
The obfuscation logic was done with great help from hpa@. And the main idea was to have ordered results after obfuscation. Per-type noise increase randomization of results. So Andrew, I actually dont know what to add here. We don't want to provide kernel order back to user-space in naked manner.
Quoted 3 lines
The obfuscation logic is to provide a 1:1 mapping but which doesn't preserve ordering, thereby avoid leaking information of kernel pointers to user space.
Oh, OK... Ignore my previous note then. But we should lose the comment that implies we have an ordering?
H. Peter Anvin
reviewerQuoted 6 lines · On 04/10/2012 04:58 PM, [email protected] wrote:
I'm not able to convince myself that "less than" and "greater than" mean anything - do we have a good proof that for all v1 and v2, the obfuscated pointers have the same ordering as the original pointers?
No, of course they don't.
That's the point.
The whole point is that kcmp() exports an ordered set, but the ordering is explicitly and intentionally different than the actual ordering in memory. It is still valid for sorting, in particular.
Quoted 6 lines
Hmm... consider the simplified example v1 = 5 and v2= 16., and cookies[0] is also 16. Then obfus(v1) == 21, and obfus(v2) == 0, and the ordering is different. So I'm thinking 0 and 3 are the only sane return values?
Or do I need more caffeine?
You need more caffeine. You're thinking about the problem wrong.
-hpa
Quoted 14 lines · Oleg Nesterov
Quoted 5 lines · H. Peter Anvin
Quoted 3 lines · Oleg Nesterov
OK, since this is discussed again...
Quoted 7 lines
Quoted 3 lines
Can this comment can also explain why do we obfuscate the pointers by type? I mean, I don't really understand why the one-dimensional cookies[2] is "not enough" from security pov.
Because it's cheap. "Just enough" is not what you want to shoot for, ever, you want to get past the "just enough" point and then consider "what can I get for cheap at this point"?
Quoted 6 lines
OK, I am not arguing. Just I thought that the small note like "we are doing this per-type to obfuscate even more" can help. I wouldn't have asked, but Cyrill rewrites this comment anyway.
Perhaps this is just me, but my first (and wrong) impression was that somehow this is needed for correctness.
Hi Oleg, would the form below sounds good?
---
Subject: Add a comment on kcmp obfuscation method
--- kernel/kcmp.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+)
kernel/kcmp.c +14 -0
@@ -17,6 +17,20 @@* reasons, still the comparison results should be suitable for * sorting. Thus, we obfuscate kernel pointers values and compare * the production instead. +* +* The obfuscation is done in two steps. First -- we use xor on +* kernel pointer with random value, which puts pointer into +* a new position in reordered space. Second -- we multiply +* the xor production with big odd random number to permute +* bits even more (the odd multiplier guarantees that the product +* is unique ever after the high bits are truncated, since any odd +* number is relative prime to 2^n). +* +* The obfuscation is done with per-type cookie values to increase +* initial entropy of results. +* +* Note also the obfuscation itself is invisible to user-space +* and if needed it can be changed to any suitable scheme. */ static unsigned long cookies[KCMP_TYPES ][2 ]__read_mostly ;
Quoted 10 lines · Cyrill Gorcunov
Quoted 6 lines · Oleg Nesterov
OK, I am not arguing. Just I thought that the small note like "we are doing this per-type to obfuscate even more" can help. I wouldn't have asked, but Cyrill rewrites this comment anyway.
Perhaps this is just me, but my first (and wrong) impression was that somehow this is needed for correctness.
Hi Oleg, would the form below sounds good?
Sure, thanks.
Oleg.