Using {memcpy,strncpy,strcpy,kstrdup} to copy the task comm relies on the
length of task comm. Changes in the task comm could result in a destination
string that is overflow. Therefore, we should explicitly ensure the destination
string is always NUL-terminated, regardless of the task comm. This approach
will facilitate future extensions to the task comm.
As suggested by Linus [0], we can identify all relevant code with the
following git grep command:
git grep 'memcpy.*->comm\>'
git grep 'kstrdup.*->comm\>'
git grep 'strncpy.*->comm\>'
git grep 'strcpy.*->comm\>'
PATCH #2~#4: memcpy
PATCH #5: kstrdup
PATCH #6~#8: strncpy
PATCH #9~#10: strcpy
Suggested-by: Linus Torvalds <torvalds@linux-foundation.org>
Link: https://lore.kernel.org/all/CAHk-=wjAmmHUg6vho1KjzQi2=psR30+CogFd4aXrThr2gsiS4g@mail.gmail.com/ [0]
Changes:
v1->v2:
- Add comment for dropping task_lock() in __get_task_comm() (Alexei)
- Drop changes in trace event (Steven)
- Fix comment on task comm (Matus)
v1: https://lore.kernel.org/all/20240602023754.25443-1-laoar.shao@gmail.com/
Yafang Shao (10):
fs/exec: Drop task_lock() inside __get_task_comm()
auditsc: Replace memcpy() with __get_task_comm()
security: Replace memcpy() with __get_task_comm()
bpftool: Ensure task comm is always NUL-terminated
mm/util: Fix possible race condition in kstrdup()
mm/kmemleak: Replace strncpy() with __get_task_comm()
tsacct: Replace strncpy() with __get_task_comm()
tracing: Replace strncpy() with __get_task_comm()
net: Replace strcpy() with __get_task_comm()
drm: Replace strcpy() with __get_task_comm()
drivers/gpu/drm/drm_framebuffer.c | 2 +-
drivers/gpu/drm/i915/i915_gpu_error.c | 2 +-
fs/exec.c | 10 ++++++++--
include/linux/sched.h | 4 ++--
kernel/auditsc.c | 6 +++---
kernel/trace/trace.c | 2 +-
kernel/trace/trace_events_hist.c | 2 +-
kernel/tsacct.c | 2 +-
mm/kmemleak.c | 8 +-------
mm/util.c | 4 +++-
net/ipv6/ndisc.c | 2 +-
security/lsm_audit.c | 4 ++--
security/selinux/selinuxfs.c | 2 +-
tools/bpf/bpftool/pids.c | 2 ++
14 files changed, 28 insertions(+), 24 deletions(-)
--
2.39.1
Quoted from Linus [0]:
Since user space can randomly change their names anyway, using locking
was always wrong for readers (for writers it probably does make sense
to have some lock - although practically speaking nobody cares there
either, but at least for a writer some kind of race could have
long-term mixed results
Suggested-by: Linus Torvalds <torvalds@linux-foundation.org>
Link: https://lore.kernel.org/all/CAHk-=wivfrF0_zvf+oj6==Sh=-npJooP8chLPEfaFV0oNYTTBA@mail.gmail.com [0]
Signed-off-by: Yafang Shao <redacted>
Cc: Alexander Viro <viro@zeniv.linux.org.uk>
Cc: Christian Brauner <brauner@kernel.org>
Cc: Jan Kara <jack@suse.cz>
Cc: Eric Biederman <redacted>
Cc: Kees Cook <redacted>
Cc: Alexei Starovoitov <redacted>
Cc: Matus Jokay <redacted>
---
fs/exec.c | 10 ++++++++--
include/linux/sched.h | 4 ++--
2 files changed, 10 insertions(+), 4 deletions(-)
Using __get_task_comm() to read the task comm ensures that the name is
always NUL-terminated, regardless of the source string. This approach also
facilitates future extensions to the task comm.
Signed-off-by: Yafang Shao <redacted>
Acked-by: Paul Moore <paul@paul-moore.com>
Cc: Eric Paris <eparis@redhat.com>
---
kernel/auditsc.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
Quoted from Linus [0]:
selinux never wanted a lock, and never wanted any kind of *consistent*
result, it just wanted a *stable* result.
Using __get_task_comm() to read the task comm ensures that the name is
always NUL-terminated, regardless of the source string. This approach also
facilitates future extensions to the task comm.
Signed-off-by: Yafang Shao <redacted>
LINK: https://lore.kernel.org/all/CAHk-=wivfrF0_zvf+oj6==Sh=-npJooP8chLPEfaFV0oNYTTBA@mail.gmail.com/ [0]
Acked-by: Paul Moore <paul@paul-moore.com>
Cc: James Morris <jmorris@namei.org>
Cc: "Serge E. Hallyn" <serge@hallyn.com>
Cc: Stephen Smalley <stephen.smalley.work@gmail.com>
Cc: Ondrej Mosnacek <redacted>
---
security/lsm_audit.c | 4 ++--
security/selinux/selinuxfs.c | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
@@ -708,7 +708,7 @@ static ssize_t sel_write_checkreqprot(struct file *file, const char __user *buf,if(new_value){charcomm[sizeof(current->comm)];-memcpy(comm,current->comm,sizeof(comm));+__get_task_comm(comm,sizeof(comm),current);pr_err("SELinux: %s (%d) set checkreqprot to 1. This is no longer supported.\n",comm,current->pid);}
Let's explicitly ensure the destination string is NUL-terminated. This way,
it won't be affected by changes to the source string.
Signed-off-by: Yafang Shao <redacted>
Reviewed-by: Quentin Monnet <qmo@kernel.org>
---
tools/bpf/bpftool/pids.c | 2 ++
1 file changed, 2 insertions(+)
In kstrdup(), it is critical to ensure that the dest string is always
NUL-terminated. However, potential race condidtion can occur between a
writer and a reader.
Consider the following scenario involving task->comm:
reader writer
len = strlen(s) + 1;
strlcpy(tsk->comm, buf, sizeof(tsk->comm));
memcpy(buf, s, len);
In this case, there is a race condition between the reader and the
writer. The reader calculate the length of the string `s` based on the
old value of task->comm. However, during the memcpy(), the string `s`
might be updated by the writer to a new value of task->comm.
If the new task->comm is larger than the old one, the `buf` might not be
NUL-terminated. This can lead to undefined behavior and potential
security vulnerabilities.
Let's fix it by explicitly adding a NUL-terminator.
Signed-off-by: Yafang Shao <redacted>
Cc: Andrew Morton <akpm@linux-foundation.org>
---
mm/util.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
Using __get_task_comm() to read the task comm ensures that the name is
always NUL-terminated, regardless of the source string. This approach also
facilitates future extensions to the task comm.
Signed-off-by: Yafang Shao <redacted>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
---
mm/kmemleak.c | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
Using __get_task_comm() to read the task comm ensures that the name is
always NUL-terminated, regardless of the source string. This approach also
facilitates future extensions to the task comm.
Signed-off-by: Yafang Shao <redacted>
---
kernel/tsacct.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
Using __get_task_comm() to read the task comm ensures that the name is
always NUL-terminated, regardless of the source string. This approach also
facilitates future extensions to the task comm.
Signed-off-by: Yafang Shao <redacted>
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: Masami Hiramatsu <mhiramat@kernel.org>
Cc: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
---
kernel/trace/trace.c | 2 +-
kernel/trace/trace_events_hist.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
To prevent errors from occurring when the src string is longer than the dst
string in strcpy(), we should use __get_task_comm() instead. This approach
also facilitates future extensions to the task comm.
Signed-off-by: Yafang Shao <redacted>
Cc: "David S. Miller" <davem@davemloft.net>
Cc: David Ahern <dsahern@kernel.org>
Cc: Eric Dumazet <edumazet@google.com>
Cc: Jakub Kicinski <kuba@kernel.org>
Cc: Paolo Abeni <pabeni@redhat.com>
---
net/ipv6/ndisc.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
To prevent erros from occurring when the src string is longer than the
dst string in strcpy(), we should use __get_task_comm() instead. This
approach also facilitates future extensions to the task comm.
Signed-off-by: Yafang Shao <redacted>
Cc: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Cc: Maxime Ripard <mripard@kernel.org>
Cc: Thomas Zimmermann <tzimmermann@suse.de>
Cc: David Airlie <airlied@gmail.com>
Cc: Daniel Vetter <redacted>
---
drivers/gpu/drm/drm_framebuffer.c | 2 +-
drivers/gpu/drm/i915/i915_gpu_error.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
On Thu, Jun 13, 2024 at 10:30:40AM +0800, Yafang Shao wrote:
quoted hunk
Using __get_task_comm() to read the task comm ensures that the name is
always NUL-terminated, regardless of the source string. This approach also
facilitates future extensions to the task comm.
Signed-off-by: Yafang Shao <redacted>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
---
mm/kmemleak.c | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
You deleted the comment stating why it does not use get_task_comm()
without explaining why it would be safe now. I don't recall the details
but most likely lockdep warned of some potential deadlocks with this
function being called with the task_lock held.
So, you either show why this is safe or just use strscpy() directly here
(not sure we'd need strscpy_pad(); I think strscpy() would do, we just
need the NUL-termination).
--
Catalin
On Thu, Jun 13, 2024 at 4:37 PM Catalin Marinas [off-list ref] wrote:
On Thu, Jun 13, 2024 at 10:30:40AM +0800, Yafang Shao wrote:
quoted
Using __get_task_comm() to read the task comm ensures that the name is
always NUL-terminated, regardless of the source string. This approach also
facilitates future extensions to the task comm.
Signed-off-by: Yafang Shao <redacted>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
---
mm/kmemleak.c | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
You deleted the comment stating why it does not use get_task_comm()
without explaining why it would be safe now. I don't recall the details
but most likely lockdep warned of some potential deadlocks with this
function being called with the task_lock held.
So, you either show why this is safe or just use strscpy() directly here
(not sure we'd need strscpy_pad(); I think strscpy() would do, we just
need the NUL-termination).
From: Andrew Morton <akpm@linux-foundation.org> Date: 2024-06-13 21:14:37
On Thu, 13 Jun 2024 10:30:39 +0800 Yafang Shao [off-list ref] wrote:
In kstrdup(), it is critical to ensure that the dest string is always
NUL-terminated. However, potential race condidtion can occur between a
writer and a reader.
Consider the following scenario involving task->comm:
reader writer
len = strlen(s) + 1;
strlcpy(tsk->comm, buf, sizeof(tsk->comm));
memcpy(buf, s, len);
In this case, there is a race condition between the reader and the
writer. The reader calculate the length of the string `s` based on the
old value of task->comm. However, during the memcpy(), the string `s`
might be updated by the writer to a new value of task->comm.
If the new task->comm is larger than the old one, the `buf` might not be
NUL-terminated. This can lead to undefined behavior and potential
security vulnerabilities.
Let's fix it by explicitly adding a NUL-terminator.
The concept sounds a little strange. If some code takes a copy of a
string while some other code is altering it, yes, the result will be a
mess. This is why get_task_comm() exists, and why it uses locking.
I get that "your copy is a mess" is less serious than "your string
isn't null-terminated" but still. Whichever outcome we get, the
calling code is buggy and should be fixed.
Are there any other problematic scenarios we're defending against here?
Now I'll start receiving patches to remove this again. Let's have a
code comment please.
And kstrdup() is now looking awfully similar to kstrndup(). Perhaps
there's a way to reduce duplication?
On Thu, 13 Jun 2024 at 14:14, Andrew Morton [off-list ref] wrote:
The concept sounds a little strange. If some code takes a copy of a
string while some other code is altering it, yes, the result will be a
mess. This is why get_task_comm() exists, and why it uses locking.
The thing is, get_task_comm() is terminally broken.
Nobody sane uses it, and sometimes it's literally _because_ it uses locking.
Let's look at the numbers:
- 39 uses of get_task_comm()
- 2 uses of __get_task_comm() because the locking doesn't work
- 447 uses of raw "current->comm"
- 112 uses of raw 'ta*sk->comm' (and possibly
IOW, we need to just accept the fact that nobody actually wants to use
"get_task_comm()". It's a broken interface. It's inconvenient, and the
locking makes it worse.
Now, I'm not convinced that kstrdup() is what anybody should use
should, but of the 600 "raw" uses of ->comm, four of them do seem to
be kstrdup.
Not great, I think they could be removed, but they are examples of
people doing this. And I think it *would* be good to have the
guarantee that yes, the kstrdup() result is always a proper string,
even if it's used for unstable sources. Who knows what other unstable
sources exist?
I do suspect that most of the raw uses of 'xyz->comm' is for
printouts. And I think we would be better with a '%pTSK' vsnprintf()
format thing for that.
Sadly, I don't think coccinelle can do the kinds of transforms that
involve printf format strings.
And no, a printk() string still couldn't use the locking version.
Linus
On Fri, Jun 14, 2024 at 5:14 AM Andrew Morton [off-list ref] wrote:
On Thu, 13 Jun 2024 10:30:39 +0800 Yafang Shao [off-list ref] wrote:
quoted
In kstrdup(), it is critical to ensure that the dest string is always
NUL-terminated. However, potential race condidtion can occur between a
writer and a reader.
Consider the following scenario involving task->comm:
reader writer
len = strlen(s) + 1;
strlcpy(tsk->comm, buf, sizeof(tsk->comm));
memcpy(buf, s, len);
In this case, there is a race condition between the reader and the
writer. The reader calculate the length of the string `s` based on the
old value of task->comm. However, during the memcpy(), the string `s`
might be updated by the writer to a new value of task->comm.
If the new task->comm is larger than the old one, the `buf` might not be
NUL-terminated. This can lead to undefined behavior and potential
security vulnerabilities.
Let's fix it by explicitly adding a NUL-terminator.
The concept sounds a little strange. If some code takes a copy of a
string while some other code is altering it, yes, the result will be a
mess. This is why get_task_comm() exists, and why it uses locking.
I get that "your copy is a mess" is less serious than "your string
isn't null-terminated" but still. Whichever outcome we get, the
calling code is buggy and should be fixed.
Are there any other problematic scenarios we're defending against here?
On Fri, Jun 14, 2024 at 6:18 AM Linus Torvalds
[off-list ref] wrote:
On Thu, 13 Jun 2024 at 14:14, Andrew Morton [off-list ref] wrote:
quoted
The concept sounds a little strange. If some code takes a copy of a
string while some other code is altering it, yes, the result will be a
mess. This is why get_task_comm() exists, and why it uses locking.
The thing is, get_task_comm() is terminally broken.
Nobody sane uses it, and sometimes it's literally _because_ it uses locking.
Let's look at the numbers:
- 39 uses of get_task_comm()
- 2 uses of __get_task_comm() because the locking doesn't work
- 447 uses of raw "current->comm"
- 112 uses of raw 'ta*sk->comm' (and possibly
IOW, we need to just accept the fact that nobody actually wants to use
"get_task_comm()". It's a broken interface. It's inconvenient, and the
locking makes it worse.
Now, I'm not convinced that kstrdup() is what anybody should use
should, but of the 600 "raw" uses of ->comm, four of them do seem to
be kstrdup.
Not great, I think they could be removed, but they are examples of
people doing this. And I think it *would* be good to have the
guarantee that yes, the kstrdup() result is always a proper string,
even if it's used for unstable sources. Who knows what other unstable
sources exist?
I do suspect that most of the raw uses of 'xyz->comm' is for
printouts. And I think we would be better with a '%pTSK' vsnprintf()
format thing for that.
I will implement this change in the next step if no one else handles it.
Sadly, I don't think coccinelle can do the kinds of transforms that
involve printf format strings.
Yes, we need to carefully check them one by one.
And no, a printk() string still couldn't use the locking version.
Linus
On Thu, Jun 13, 2024 at 08:10:17PM +0800, Yafang Shao wrote:
On Thu, Jun 13, 2024 at 4:37 PM Catalin Marinas [off-list ref] wrote:
quoted
On Thu, Jun 13, 2024 at 10:30:40AM +0800, Yafang Shao wrote:
quoted
Using __get_task_comm() to read the task comm ensures that the name is
always NUL-terminated, regardless of the source string. This approach also
facilitates future extensions to the task comm.
Signed-off-by: Yafang Shao <redacted>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
---
mm/kmemleak.c | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
You deleted the comment stating why it does not use get_task_comm()
without explaining why it would be safe now. I don't recall the details
but most likely lockdep warned of some potential deadlocks with this
function being called with the task_lock held.
So, you either show why this is safe or just use strscpy() directly here
(not sure we'd need strscpy_pad(); I think strscpy() would do, we just
need the NUL-termination).
Ah, great. For this patch:
Acked-by: Catalin Marinas <catalin.marinas@arm.com>
You may want to add a comment in the commit log that since
__get_task_comm() no longer takes a long, it's safe to call it from
kmemleak.
--
Catalin
On Fri, Jun 14, 2024 at 6:57 PM Catalin Marinas [off-list ref] wrote:
On Thu, Jun 13, 2024 at 08:10:17PM +0800, Yafang Shao wrote:
quoted
On Thu, Jun 13, 2024 at 4:37 PM Catalin Marinas [off-list ref] wrote:
quoted
On Thu, Jun 13, 2024 at 10:30:40AM +0800, Yafang Shao wrote:
quoted
Using __get_task_comm() to read the task comm ensures that the name is
always NUL-terminated, regardless of the source string. This approach also
facilitates future extensions to the task comm.
Signed-off-by: Yafang Shao <redacted>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
---
mm/kmemleak.c | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
You deleted the comment stating why it does not use get_task_comm()
without explaining why it would be safe now. I don't recall the details
but most likely lockdep warned of some potential deadlocks with this
function being called with the task_lock held.
So, you either show why this is safe or just use strscpy() directly here
(not sure we'd need strscpy_pad(); I think strscpy() would do, we just
need the NUL-termination).
Ah, great. For this patch:
Acked-by: Catalin Marinas <catalin.marinas@arm.com>
You may want to add a comment in the commit log that since
__get_task_comm() no longer takes a long, it's safe to call it from
kmemleak.
I will add it. Thanks for your suggestion.
--
Regards
Yafang