2021-08-27 09:31:40

by Helge Deller

[permalink] [raw]
Subject: [PATCH] Fix prctl(PR_GET_NAME) to not leak random trailing bytes

The prctl(PR_GET_NAME) and prctl(PR_SET_NAME) syscalls are used to set and
retrieve the process name. Those kernel functions are currently implemented to
always copy the full array of 16-bytes back and forth between kernel and
userspace instead of just copying the relevant bytes of the string.

This patch changes the prctl(PR_GET_NAME) to only copy back the null-terminated
string (with max. up to 16 chars including the trailing zero) to userspace and
thus avoids copying and leaking random trailing chars behind the process name.

Background:
The newest glibc testsuite includes a test which is implemented similiar to
this:
prctl(PR_SET_NAME, "thread name", 0, 0, 0);
char buffer[16] = { 0, };
prctl(PR_GET_NAME, buffer, 0, 0, 0);
char expected[16] = "thread name";
fail if memcmp(buffer, expected, 16) != 0;

The compiler may put the "thread name" string given in the PR_SET_NAME call
somewhere into memory and it's not guaranteed that trailing (up to a total of
16) chars behind that string has zeroes.
As such on the parisc architecture I've seen that the buffer[] array gets
filled on return of prctl(PR_GET_NAME) with such additional random bytes, e.g.:
"thread name\000@\032i\000"
74 68 72 65 61 64 20 6E 61 6D 65 00 40 1A 69 00

Unfortunatly the glibc testuite tests the full memory block of 16 bytes
and fails because it expects zeroed characters behind the process name.

In addition to fix the glibc testsuite, I suggest to fix the kernel function of
prctl(PR_GET_NAME) to just return the null-terminated process name.

Signed-off-by: Helge Deller <[email protected]>
Cc: [email protected]

diff --git a/kernel/sys.c b/kernel/sys.c
index ef1a78f5d71c..af71412760be 100644
--- a/kernel/sys.c
+++ b/kernel/sys.c
@@ -2367,7 +2367,7 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,
break;
case PR_GET_NAME:
get_task_comm(comm, me);
- if (copy_to_user((char __user *)arg2, comm, sizeof(comm)))
+ if (copy_to_user((char __user *)arg2, comm, strlen(comm) + 1))
return -EFAULT;
break;
case PR_GET_ENDIAN:


2021-08-27 10:33:21

by Rasmus Villemoes

[permalink] [raw]
Subject: Re: [PATCH] Fix prctl(PR_GET_NAME) to not leak random trailing bytes

On 27/08/2021 11.28, Helge Deller wrote:
> The prctl(PR_GET_NAME) and prctl(PR_SET_NAME) syscalls are used to set and
> retrieve the process name. Those kernel functions are currently implemented to
> always copy the full array of 16-bytes back and forth between kernel and
> userspace instead of just copying the relevant bytes of the string.
>
> This patch changes the prctl(PR_GET_NAME) to only copy back the null-terminated
> string (with max. up to 16 chars including the trailing zero) to userspace and
> thus avoids copying and leaking random trailing chars behind the process name.
>
> Background:
> The newest glibc testsuite includes a test which is implemented similiar to
> this:
> prctl(PR_SET_NAME, "thread name", 0, 0, 0);
> char buffer[16] = { 0, };
> prctl(PR_GET_NAME, buffer, 0, 0, 0);
> char expected[16] = "thread name";
> fail if memcmp(buffer, expected, 16) != 0;
>
> The compiler may put the "thread name" string given in the PR_SET_NAME call
> somewhere into memory and it's not guaranteed that trailing (up to a total of
> 16) chars behind that string has zeroes.
> As such on the parisc architecture I've seen that the buffer[] array gets
> filled on return of prctl(PR_GET_NAME) with such additional random bytes, e.g.:
> "thread name\000@\032i\000"
> 74 68 72 65 61 64 20 6E 61 6D 65 00 40 1A 69 00
>
> Unfortunatly the glibc testuite tests the full memory block of 16 bytes
> and fails because it expects zeroed characters behind the process name.
>
> In addition to fix the glibc testsuite, I suggest to fix the kernel function of
> prctl(PR_GET_NAME) to just return the null-terminated process name.
>
> Signed-off-by: Helge Deller <[email protected]>
> Cc: [email protected]
>
> diff --git a/kernel/sys.c b/kernel/sys.c
> index ef1a78f5d71c..af71412760be 100644
> --- a/kernel/sys.c
> +++ b/kernel/sys.c
> @@ -2367,7 +2367,7 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,
> break;
> case PR_GET_NAME:
> get_task_comm(comm, me);
> - if (copy_to_user((char __user *)arg2, comm, sizeof(comm)))
> + if (copy_to_user((char __user *)arg2, comm, strlen(comm) + 1))
> return -EFAULT;
> break;

I don't understand. get_task_comm() is

extern char *__get_task_comm(char *to, size_t len, struct task_struct *tsk);
#define get_task_comm(buf, tsk) ({ \
BUILD_BUG_ON(sizeof(buf) != TASK_COMM_LEN); \
__get_task_comm(buf, sizeof(buf), tsk); \
})

and __get_task_comm() is

char *__get_task_comm(char *buf, size_t buf_size, struct task_struct *tsk)
{
task_lock(tsk);
strncpy(buf, tsk->comm, buf_size);
task_unlock(tsk);
return buf;
}

so the strncpy should ensure that the caller's buffer after the string's
terminator gets zero-filled. I can see that parisc has its own
strncpy(), but I can't read that asm, so I can't see if it actually does
that mandated-by-C-standard zero-filling. It would surprise me if it
didn't (I'd expect lots of other breakage), but OTOH it is the only way
I can explain what you've seen.

[Also, the compiler most likely puts the "thread name" string literal
first into a .rodata.strX.Y section, only later to be merged into
.rodata by the linker, so anything after "thread name" I'd expect to
also be some readable string. The fact that you have a 0x1a byte in
there suggests that the garbage doesn't actually come from the original
PR_SET_NAME call, but is consistent with it being a stack leak from the
kernel. You could try running hexdump on the test binary to see if
there's any occurrence of "thread name" followed by those particular
garbage bytes.]

Rasmus

2021-08-27 12:22:27

by Helge Deller

[permalink] [raw]
Subject: Re: [PATCH] Fix prctl(PR_GET_NAME) to not leak random trailing bytes

On 8/27/21 12:31 PM, Rasmus Villemoes wrote:
> On 27/08/2021 11.28, Helge Deller wrote:
>> The prctl(PR_GET_NAME) and prctl(PR_SET_NAME) syscalls are used to set and
>> retrieve the process name. Those kernel functions are currently implemented to
>> always copy the full array of 16-bytes back and forth between kernel and
>> userspace instead of just copying the relevant bytes of the string.
>>
>> This patch changes the prctl(PR_GET_NAME) to only copy back the null-terminated
>> string (with max. up to 16 chars including the trailing zero) to userspace and
>> thus avoids copying and leaking random trailing chars behind the process name.
>>
>> Background:
>> The newest glibc testsuite includes a test which is implemented similiar to
>> this:
>> prctl(PR_SET_NAME, "thread name", 0, 0, 0);
>> char buffer[16] = { 0, };
>> prctl(PR_GET_NAME, buffer, 0, 0, 0);
>> char expected[16] = "thread name";
>> fail if memcmp(buffer, expected, 16) != 0;
>>
>> The compiler may put the "thread name" string given in the PR_SET_NAME call
>> somewhere into memory and it's not guaranteed that trailing (up to a total of
>> 16) chars behind that string has zeroes.
>> As such on the parisc architecture I've seen that the buffer[] array gets
>> filled on return of prctl(PR_GET_NAME) with such additional random bytes, e.g.:
>> "thread name\000@\032i\000"
>> 74 68 72 65 61 64 20 6E 61 6D 65 00 40 1A 69 00
>>
>> Unfortunatly the glibc testuite tests the full memory block of 16 bytes
>> and fails because it expects zeroed characters behind the process name.
>>
>> In addition to fix the glibc testsuite, I suggest to fix the kernel function of
>> prctl(PR_GET_NAME) to just return the null-terminated process name.
>>
>> Signed-off-by: Helge Deller <[email protected]>
>> Cc: [email protected]
>>
>> diff --git a/kernel/sys.c b/kernel/sys.c
>> index ef1a78f5d71c..af71412760be 100644
>> --- a/kernel/sys.c
>> +++ b/kernel/sys.c
>> @@ -2367,7 +2367,7 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,
>> break;
>> case PR_GET_NAME:
>> get_task_comm(comm, me);
>> - if (copy_to_user((char __user *)arg2, comm, sizeof(comm)))
>> + if (copy_to_user((char __user *)arg2, comm, strlen(comm) + 1))
>> return -EFAULT;
>> break;
>
> I don't understand. get_task_comm() is
>
> extern char *__get_task_comm(char *to, size_t len, struct task_struct *tsk);
> #define get_task_comm(buf, tsk) ({ \
> BUILD_BUG_ON(sizeof(buf) != TASK_COMM_LEN); \
> __get_task_comm(buf, sizeof(buf), tsk); \
> })
>
> and __get_task_comm() is
>
> char *__get_task_comm(char *buf, size_t buf_size, struct task_struct *tsk)
> {
> task_lock(tsk);
> strncpy(buf, tsk->comm, buf_size);
> task_unlock(tsk);
> return buf;
> }
>
> so the strncpy should ensure that the caller's buffer after the string's
> terminator gets zero-filled. I can see that parisc has its own
> strncpy(), but I can't read that asm, so I can't see if it actually does
> that mandated-by-C-standard zero-filling.

Oh, the parisc strncpy() asm does NOT zero-fill the target address !!
That's the bug.
I thought strncpy would just copy up to given number of chars.

> It would surprise me if it
> didn't (I'd expect lots of other breakage), but OTOH it is the only way
> I can explain what you've seen.

Interestingly the kernel runs quite well and we don't see any bigger breakage.
Anyway, the function needs fixing.

Please ignore this patch and thanks for pointing to the real bug.

Helge

2021-08-28 17:57:04

by Linus Torvalds

[permalink] [raw]
Subject: Re: [PATCH] Fix prctl(PR_GET_NAME) to not leak random trailing bytes

On Fri, Aug 27, 2021 at 5:19 AM Helge Deller <[email protected]> wrote:
>
> Oh, the parisc strncpy() asm does NOT zero-fill the target address !!
> That's the bug.
> I thought strncpy would just copy up to given number of chars.

Yeah, strncpy() is a horrible horrible function.

It doesn't copy any NUL character at all if the source length was
longer than the 'n', so it doesn't necessarily result in a
NUL-terminated string.

And it fills the target with NUL characters at the end, which is
usually horribly inefficient and means that you should never ever use
it with big destination buffers. So it can be a surprisingly bad
function to use for the - not all that unusual situation - where you
have lots of space in the destination buffer, and use the 'n' version
of strcpy() just to be safe for odd situations. It will zero-fill all
that space, usually for no good reason.

Very different from the other 'n' functions like snprintf() and
friends that people use for that same "destination buffer safety"
reason.

So it's almost never the right thing to use, even if it's the most
traditional, most common and - by name - most obvious "copy string of
at most length 'n'" function.

It so happens that "comm[]" is probably one of *very* few situations
in the kernel where we really do want to use strncpy(), and where we
don't just NUL-terminate, but NUL-fill the buffer.

Of course, that "comm[]" behavior is unusual these days, but I think
it was a lot more usual back in the early 70's, when that whole
"small, fixed-size string buffer" model was much much more common than
it is today.

It is, after all, the exact same reason why the C language linker
rules for identifiers were historically "only the first 7 letters are
necessarily significant". Because "use a fixed 8-byte buffer for a
string" made sense at the time in ways it doesn't necessarily make all
that much sense today.

So that odd and nasty behavior of strncpy() makes a lot more sense in
the historical context - it's just that that context is 50 years ago.

While mentioning all the oddities of 'strncpy()', it's also worth
noting that despite the similarities in the name,
"strncpy_from_user()" does *not* fill the end of the destination
buffer with NUL characters, and does *not* act like strncpy(). The
user string copy function obviously also has a very very different
return value, which hopefully makes it more obvious that it's a very
different beast.

Most of the time, if you actually want to copy a string, and have a
limited destination buffer, you should use 'strscpy()'. Of the
"limited size string" routines, it's pretty much the only sane one,
and it guarantees - as long as the target size is non-zero - that the
target is NUL-terminated without doing the NUL filling.

(The BSD 'strlcpy()' is horribly broken because the return value
semantics means that it will have to find the terminating NUL of the
*source* string, even if the source string is horribly long, or
untrusted and unterminated).

> Interestingly the kernel runs quite well and we don't see any bigger breakage.

Yeah, almost nothing actually cares about the odd NUL filling that -
as you - few people realize is even part of strncpy().

> Anyway, the function needs fixing.

I'd suggest you just use the default one in lib/string.c and not
override it with __HAVE_ARCH_STRNCPY.

For example, on x86, we for purely historical reasons do that
__HAVE_ARCH_STRNCPY thing, but only for the legacy 32-bit code. x86-64
uses that generic lib/string.c version (and honestly, the 32-bit
arch-specific x86 one is almost certainly much worse, but hey, it
really exists purely due to hysterical raisins).

That generic thing is a slow byte-per-byte implementation, but I don't
think we have ever had a situation where it really matters.

It's also slightly oddly implemented - notice how the way it does that
zero fill is by simply not incrementing the source pointer once it
finds a NUL character. Simple and effective (even if perhaps not
_efficient_), but it can make people miss what is going on because
it's an unusual pattern.

Linus

2021-08-28 19:38:07

by Helge Deller

[permalink] [raw]
Subject: Re: [PATCH] Fix prctl(PR_GET_NAME) to not leak random trailing bytes

* Linus Torvalds <[email protected]>:
> On Fri, Aug 27, 2021 at 5:19 AM Helge Deller <[email protected]> wrote:
> >
> > Oh, the parisc strncpy() asm does NOT zero-fill the target address !!
> > That's the bug.
> > I thought strncpy would just copy up to given number of chars.
>
> Yeah, strncpy() is a horrible horrible function.
>
> It doesn't copy any NUL character at all if the source length was
> longer than the 'n', so it doesn't necessarily result in a
> NUL-terminated string.
>
> And it fills the target with NUL characters at the end, which is
> usually horribly inefficient and means that you should never ever use
> it with big destination buffers. So it can be a surprisingly bad
> function to use for the - not all that unusual situation - where you
> have lots of space in the destination buffer, and use the 'n' version
> of strcpy() just to be safe for odd situations. It will zero-fill all
> that space, usually for no good reason.
>
> Very different from the other 'n' functions like snprintf() and
> friends that people use for that same "destination buffer safety"
> reason.
>
> So it's almost never the right thing to use, even if it's the most
> traditional, most common and - by name - most obvious "copy string of
> at most length 'n'" function.
>
> It so happens that "comm[]" is probably one of *very* few situations
> in the kernel where we really do want to use strncpy(), and where we
> don't just NUL-terminate, but NUL-fill the buffer.
>
> Of course, that "comm[]" behavior is unusual these days, but I think
> it was a lot more usual back in the early 70's, when that whole
> "small, fixed-size string buffer" model was much much more common than
> it is today.
>
> It is, after all, the exact same reason why the C language linker
> rules for identifiers were historically "only the first 7 letters are
> necessarily significant". Because "use a fixed 8-byte buffer for a
> string" made sense at the time in ways it doesn't necessarily make all
> that much sense today.
>
> So that odd and nasty behavior of strncpy() makes a lot more sense in
> the historical context - it's just that that context is 50 years ago.
>
> While mentioning all the oddities of 'strncpy()', it's also worth
> noting that despite the similarities in the name,
> "strncpy_from_user()" does *not* fill the end of the destination
> buffer with NUL characters, and does *not* act like strncpy(). The
> user string copy function obviously also has a very very different
> return value, which hopefully makes it more obvious that it's a very
> different beast.
>
> Most of the time, if you actually want to copy a string, and have a
> limited destination buffer, you should use 'strscpy()'. Of the
> "limited size string" routines, it's pretty much the only sane one,
> and it guarantees - as long as the target size is non-zero - that the
> target is NUL-terminated without doing the NUL filling.
>
> (The BSD 'strlcpy()' is horribly broken because the return value
> semantics means that it will have to find the terminating NUL of the
> *source* string, even if the source string is horribly long, or
> untrusted and unterminated).

Thanks for the explanations!


> > Interestingly the kernel runs quite well and we don't see any bigger breakage.
>
> Yeah, almost nothing actually cares about the odd NUL filling that -
> as you - few people realize is even part of strncpy().
>
> > Anyway, the function needs fixing.
>
> I'd suggest you just use the default one in lib/string.c and not
> override it with __HAVE_ARCH_STRNCPY.

Yes, that's probably the best idea.

Would you be willing to apply the attached patch for v5.14 ?

It reverts the original commit in which I introduced the
assembly str*() functions and adjusts the SPDX header tag.
It has been in my for-next tree since yesterday.

Thanks,
Helge

--

From: Helge Deller <[email protected]>
Date: Fri, 27 Aug 2021 20:42:57 +0200
Subject: [PATCH] Revert "parisc: Add assembly implementations for memset,
strlen, strcpy, strncpy and strcat"

This reverts commit 83af58f8068ea3f7b3c537c37a30887bfa585069.

It turns out that at least the assembly implementation for strncpy() was
buggy. Revert the whole commit and return back to the default coding.

Signed-off-by: Helge Deller <[email protected]>
Cc: <[email protected]> # v5.4+

diff --git a/arch/parisc/include/asm/string.h b/arch/parisc/include/asm/string.h
index 4a0c9dbd62fd..f6e1132f4e35 100644
--- a/arch/parisc/include/asm/string.h
+++ b/arch/parisc/include/asm/string.h
@@ -8,19 +8,4 @@ extern void * memset(void *, int, size_t);
#define __HAVE_ARCH_MEMCPY
void * memcpy(void * dest,const void *src,size_t count);

-#define __HAVE_ARCH_STRLEN
-extern size_t strlen(const char *s);
-
-#define __HAVE_ARCH_STRCPY
-extern char *strcpy(char *dest, const char *src);
-
-#define __HAVE_ARCH_STRNCPY
-extern char *strncpy(char *dest, const char *src, size_t count);
-
-#define __HAVE_ARCH_STRCAT
-extern char *strcat(char *dest, const char *src);
-
-#define __HAVE_ARCH_MEMSET
-extern void *memset(void *, int, size_t);
-
#endif
diff --git a/arch/parisc/kernel/parisc_ksyms.c b/arch/parisc/kernel/parisc_ksyms.c
index 8ed409ecec93..e8a6a751dfd8 100644
--- a/arch/parisc/kernel/parisc_ksyms.c
+++ b/arch/parisc/kernel/parisc_ksyms.c
@@ -17,10 +17,6 @@

#include <linux/string.h>
EXPORT_SYMBOL(memset);
-EXPORT_SYMBOL(strlen);
-EXPORT_SYMBOL(strcpy);
-EXPORT_SYMBOL(strncpy);
-EXPORT_SYMBOL(strcat);

#include <linux/atomic.h>
EXPORT_SYMBOL(__xchg8);
diff --git a/arch/parisc/lib/Makefile b/arch/parisc/lib/Makefile
index 2d7a9974dbae..7b197667faf6 100644
--- a/arch/parisc/lib/Makefile
+++ b/arch/parisc/lib/Makefile
@@ -3,7 +3,7 @@
# Makefile for parisc-specific library files
#

-lib-y := lusercopy.o bitops.o checksum.o io.o memcpy.o \
- ucmpdi2.o delay.o string.o
+lib-y := lusercopy.o bitops.o checksum.o io.o memset.o memcpy.o \
+ ucmpdi2.o delay.o

obj-y := iomap.o
diff --git a/arch/parisc/lib/memset.c b/arch/parisc/lib/memset.c
new file mode 100644
index 000000000000..133e4809859a
--- /dev/null
+++ b/arch/parisc/lib/memset.c
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#include <linux/types.h>
+#include <asm/string.h>
+
+#define OPSIZ (BITS_PER_LONG/8)
+typedef unsigned long op_t;
+
+void *
+memset (void *dstpp, int sc, size_t len)
+{
+ unsigned int c = sc;
+ long int dstp = (long int) dstpp;
+
+ if (len >= 8)
+ {
+ size_t xlen;
+ op_t cccc;
+
+ cccc = (unsigned char) c;
+ cccc |= cccc << 8;
+ cccc |= cccc << 16;
+ if (OPSIZ > 4)
+ /* Do the shift in two steps to avoid warning if long has 32 bits. */
+ cccc |= (cccc << 16) << 16;
+
+ /* There are at least some bytes to set.
+ No need to test for LEN == 0 in this alignment loop. */
+ while (dstp % OPSIZ != 0)
+ {
+ ((unsigned char *) dstp)[0] = c;
+ dstp += 1;
+ len -= 1;
+ }
+
+ /* Write 8 `op_t' per iteration until less than 8 `op_t' remain. */
+ xlen = len / (OPSIZ * 8);
+ while (xlen > 0)
+ {
+ ((op_t *) dstp)[0] = cccc;
+ ((op_t *) dstp)[1] = cccc;
+ ((op_t *) dstp)[2] = cccc;
+ ((op_t *) dstp)[3] = cccc;
+ ((op_t *) dstp)[4] = cccc;
+ ((op_t *) dstp)[5] = cccc;
+ ((op_t *) dstp)[6] = cccc;
+ ((op_t *) dstp)[7] = cccc;
+ dstp += 8 * OPSIZ;
+ xlen -= 1;
+ }
+ len %= OPSIZ * 8;
+
+ /* Write 1 `op_t' per iteration until less than OPSIZ bytes remain. */
+ xlen = len / OPSIZ;
+ while (xlen > 0)
+ {
+ ((op_t *) dstp)[0] = cccc;
+ dstp += OPSIZ;
+ xlen -= 1;
+ }
+ len %= OPSIZ;
+ }
+
+ /* Write the last few bytes. */
+ while (len > 0)
+ {
+ ((unsigned char *) dstp)[0] = c;
+ dstp += 1;
+ len -= 1;
+ }
+
+ return dstpp;
+}
diff --git a/arch/parisc/lib/string.S b/arch/parisc/lib/string.S
deleted file mode 100644
index 4a64264427a6..000000000000
--- a/arch/parisc/lib/string.S
+++ /dev/null
@@ -1,136 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * PA-RISC assembly string functions
- *
- * Copyright (C) 2019 Helge Deller <[email protected]>
- */
-
-#include <asm/assembly.h>
-#include <linux/linkage.h>
-
- .section .text.hot
- .level PA_ASM_LEVEL
-
- t0 = r20
- t1 = r21
- t2 = r22
-
-ENTRY_CFI(strlen, frame=0,no_calls)
- or,COND(<>) arg0,r0,ret0
- b,l,n .Lstrlen_null_ptr,r0
- depwi 0,31,2,ret0
- cmpb,COND(<>) arg0,ret0,.Lstrlen_not_aligned
- ldw,ma 4(ret0),t0
- cmpib,tr 0,r0,.Lstrlen_loop
- uxor,nbz r0,t0,r0
-.Lstrlen_not_aligned:
- uaddcm arg0,ret0,t1
- shladd t1,3,r0,t1
- mtsar t1
- depwi -1,%sar,32,t0
- uxor,nbz r0,t0,r0
-.Lstrlen_loop:
- b,l,n .Lstrlen_end_loop,r0
- ldw,ma 4(ret0),t0
- cmpib,tr 0,r0,.Lstrlen_loop
- uxor,nbz r0,t0,r0
-.Lstrlen_end_loop:
- extrw,u,<> t0,7,8,r0
- addib,tr,n -3,ret0,.Lstrlen_out
- extrw,u,<> t0,15,8,r0
- addib,tr,n -2,ret0,.Lstrlen_out
- extrw,u,<> t0,23,8,r0
- addi -1,ret0,ret0
-.Lstrlen_out:
- bv r0(rp)
- uaddcm ret0,arg0,ret0
-.Lstrlen_null_ptr:
- bv,n r0(rp)
-ENDPROC_CFI(strlen)
-
-
-ENTRY_CFI(strcpy, frame=0,no_calls)
- ldb 0(arg1),t0
- stb t0,0(arg0)
- ldo 0(arg0),ret0
- ldo 1(arg1),t1
- cmpb,= r0,t0,2f
- ldo 1(arg0),t2
-1: ldb 0(t1),arg1
- stb arg1,0(t2)
- ldo 1(t1),t1
- cmpb,<> r0,arg1,1b
- ldo 1(t2),t2
-2: bv,n r0(rp)
-ENDPROC_CFI(strcpy)
-
-
-ENTRY_CFI(strncpy, frame=0,no_calls)
- ldb 0(arg1),t0
- stb t0,0(arg0)
- ldo 1(arg1),t1
- ldo 0(arg0),ret0
- cmpb,= r0,t0,2f
- ldo 1(arg0),arg1
-1: ldo -1(arg2),arg2
- cmpb,COND(=),n r0,arg2,2f
- ldb 0(t1),arg0
- stb arg0,0(arg1)
- ldo 1(t1),t1
- cmpb,<> r0,arg0,1b
- ldo 1(arg1),arg1
-2: bv,n r0(rp)
-ENDPROC_CFI(strncpy)
-
-
-ENTRY_CFI(strcat, frame=0,no_calls)
- ldb 0(arg0),t0
- cmpb,= t0,r0,2f
- ldo 0(arg0),ret0
- ldo 1(arg0),arg0
-1: ldb 0(arg0),t1
- cmpb,<>,n r0,t1,1b
- ldo 1(arg0),arg0
-2: ldb 0(arg1),t2
- stb t2,0(arg0)
- ldo 1(arg0),arg0
- ldb 0(arg1),t0
- cmpb,<> r0,t0,2b
- ldo 1(arg1),arg1
- bv,n r0(rp)
-ENDPROC_CFI(strcat)
-
-
-ENTRY_CFI(memset, frame=0,no_calls)
- copy arg0,ret0
- cmpb,COND(=) r0,arg0,4f
- copy arg0,t2
- cmpb,COND(=) r0,arg2,4f
- ldo -1(arg2),arg3
- subi -1,arg3,t0
- subi 0,t0,t1
- cmpiclr,COND(>=) 0,t1,arg2
- ldo -1(t1),arg2
- extru arg2,31,2,arg0
-2: stb arg1,0(t2)
- ldo 1(t2),t2
- addib,>= -1,arg0,2b
- ldo -1(arg3),arg3
- cmpiclr,COND(<=) 4,arg2,r0
- b,l,n 4f,r0
-#ifdef CONFIG_64BIT
- depd,* r0,63,2,arg2
-#else
- depw r0,31,2,arg2
-#endif
- ldo 1(t2),t2
-3: stb arg1,-1(t2)
- stb arg1,0(t2)
- stb arg1,1(t2)
- stb arg1,2(t2)
- addib,COND(>) -4,arg2,3b
- ldo 4(t2),t2
-4: bv,n r0(rp)
-ENDPROC_CFI(memset)
-
- .end