Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ql_syscall_shmat is dummy implementation and sometimes returns 0 address #1331

Closed
SRSG opened this issue Mar 29, 2023 · 26 comments
Closed

ql_syscall_shmat is dummy implementation and sometimes returns 0 address #1331

SRSG opened this issue Mar 29, 2023 · 26 comments
Assignees

Comments

@SRSG
Copy link

SRSG commented Mar 29, 2023

Is your feature request related to a problem? Please describe.
When I simulated the Tenda /bin/webs program, I found that while qiling has successfully implemented shmget, shmat was just dummy implementation.This has been written as a comment at line 246,https://github.com/qilingframework/qiling/blob/master/qiling/os/posix/syscall/mman.py.

During the simulation process, at first ,I got an error "syscall ql_syscall_ipc number = 0x1015(4117) not implemented".Before long I wrote my_syscall_ipc(code as below and only part of real syscall_ipc) to solve it.

def my_syscall_ipc(ql:Qiling, call: int, first: int, second: int, third: int, ptr: int, fifth: int):
    version = call >> 16
    call &= 0xffff
    if call == 23:
        return ql_syscall_shmget(ql, first, second, third)
    elif call == 21:
        if version != 1:
            ret = ql_syscall_shmat(ql, first, ptr, second)
            return ret
        elif version == 1:
            return -EINVAL
    else:
        return -ENOSYS

This syscall_ipc will call another 2 syscalls--shmget and shmat.Then an new error occured.shmat returned address 0 which cannot be used.Possible error part of qiling_syscall_shmat are as below:

 if shmaddr == 0:
        addr = ql.mem.map_anywhere(size)
else:
        addr = ql.mem.map(shmaddr, size, info="[shm]")
return addr

When the second argument shmaddr is 0,ql.mem.map_anywhere will return 0 address.

Describe the solution you'd like
Generally, most programs might check return value like if(ret) report error;else rigth step.So does qiling plan to correctly implement shmat or check why it return 0?

@elicn
Copy link
Member

elicn commented Apr 3, 2023

Thanks for reporting this.
I've just pushed a pack of maintenance fixes (#1336) and included a new implementation for shmget, shmat and ipc.
Can you give it a try and let me know if it solves the problem?

@SRSG
Copy link
Author

SRSG commented Apr 6, 2023

It doesn't seem to work.Actually in shmget you assign the return value of map_anywhere() to key.For no limit on minaddr,shmget always return 0(that is key = 0) first.Then shmat will use 0 address, which the program cannot use yet and exit -1.
You may see it like this:
796dc76cb2f5e3256574c6375ec4226
By the way I'd like to thanks for your work.May you try to fix it again?

@elicn
Copy link
Member

elicn commented Apr 7, 2023

Good point. I'll implement an incrementing key value instead of just using the address.
Any ideas about what should be the minimal allocation address? I haven't found a formal guideline (we could use the mmap minimal address for that, I guess).

@elicn elicn self-assigned this Apr 7, 2023
@elicn
Copy link
Member

elicn commented Apr 7, 2023

@SRSG, I added the fix.
Can you pull the PR again and give it another try?

@SRSG
Copy link
Author

SRSG commented Apr 7, 2023

I'd like to try it soon.By the way ,when I debug by IDA pro,I find that shmat(maybe in uclibc) require the return address greater than 0XFFFC0001 or it will be replaced by 0.It is quite strange.But I didn't debug it carefully, so it's only for reference.

@SRSG
Copy link
Author

SRSG commented Apr 7, 2023

Your change to the address has worked but the PR can't run yet.
image
I traced the shmat(),which is in /lib/libuClibc-0.9.33.so for the program, again in IDA.It is as follow:
image
It's true that the addr is compared with 0xFFFC0001.If addr < 0xFFFC0001,the result will be reset to 0.That's why even if you have changed the address to 0x90001000, it is still < 0xFFFC0001 and shmat reset it to 0.So the PR exits for the 0.
The assembly code for the comparison jump section is as follows

li      $v1, 0xFFFC0001
sltu    $v1, $v0, $v1
beqz    $v1, loc_26540
nop

lw      $v0, 0x2C+var_C($sp)

loc_26540:                               # CODE XREF: shmat+44↑j
lw      $ra, 0x2C+var_s0($sp)
nop
jr      $ra
addiu   $sp, 0x30

@elicn
Copy link
Member

elicn commented Apr 8, 2023

I didn't find any reference to this number in the man pages; if you can point me, that would be great.
I did see though that shmat uses mmap under the hood, so they might share the same requirements as I suspected. Anyway, this is configurable on your end: go to "qiling/profiles/linux.ql" and change the value for mmap_address to the minimum you require and see if it works for you.

@SRSG
Copy link
Author

SRSG commented Apr 9, 2023

That the number is not found possibly due to the version.My version of uclibc is 0.9.33.And in this version ,the shmat() is like this :
image
Compared to the C code decompiled by IDA,which I posted earlier, they're very similar, isn't it?Maybe you can study this version.There might be some connection between the number and SHMLBA.
Modify linux.ql seems to be feasible.We can add address in linux.ql and then read and use them.But it needs further test.The code of "read" may be not implement and some address might cause mmap error.

@elicn
Copy link
Member

elicn commented Apr 14, 2023

I re-implemented shmget and shmat and added shmdt.
Among the changes I also considered the SHMLBA alignment, which is different in MIPS (0x40000 instead of 0x1000).
Can you give it a try?

@SRSG
Copy link
Author

SRSG commented Apr 19, 2023

Your latest changes may have gone wrong.
1
For the last ipc(shmat),its second is 0 which means it should return ptr.This ran correctly in the last change,but in this change it goes wrong.

@SRSG
Copy link
Author

SRSG commented Apr 19, 2023

But I'd like to appreciate it that your changes about that number I mentioned and SHMLBA work and solve the problem.For this part,your changes have gone correct.

@elicn
Copy link
Member

elicn commented Apr 19, 2023

It seems like the address 0x50000000 was already taken, so it can't attach segment at shmaddr. In that case -1 should be returned.

Do you think it should have behaved differently? If so, can you point to a specific flow there you think should happen?
Please refer to:

@SRSG
Copy link
Author

SRSG commented Apr 20, 2023

I am sorry that I was wrong, which may have caused you a misunderstanding.I want to say that the second arg of shmat that is ptr rather than second it echoes to us.

When ptr is not 0(last ipc or shmat,and it is 0x50000000),it should attach to this address.This is explained in shmat man page: https://linux.die.net/man/2/shmat

If shmaddr isn't NULL and SHM_RND is specified in shmflg, the attach occurs at the address equal to shmaddr rounded down to the nearest multiple of SHMLBA.

For my program,this ipc initialize this memory for the first time,so maybe 0x50000000 could not be taken yet.Besides,your last changes(not this time) can return 0x50000000 correctly.

p.s.In last changes it cannot work about that number 0xfffc0001 so I patched the PR to make it run till this ipc,and it return correctly.

@elicn
Copy link
Member

elicn commented Apr 20, 2023

I understood that, and mentioned that the address was already taken so the segment couldn't be attached there. In this case (not being able to attach to the specified address) it should return -1.

If the common practice is that the program allocates the memory before calling shmat, then that could be the problem. If you could attach here the full log (as a text file, not image) that would help me see the full context of the call.

As for the last thing you mentioned, sorry but I didn't understand what you meant there..

@SRSG
Copy link
Author

SRSG commented Apr 20, 2023

The full log?Do you mean the echo just run this program or the echo run with debug verbose, or a file output in somewhere?

@elicn
Copy link
Member

elicn commented Apr 20, 2023

The full log?Do you mean the echo just run this program or the echo run with debug verbose, or a file output in somewhere?

Attaching a text file containing Qiling log output, set to default verbosity.

@SRSG
Copy link
Author

SRSG commented Apr 21, 2023

Like below:

[=] 	mmap(addr = 0x0, length = 0x1000, prot = 0x3, flags = 0x4000802, fd = 0xffffffff, pgoffset = 0x0) = 0x90000000
[=] 	open(filename = 0x7ff3c2c0, flags = 0x0, mode = 0x0) = 0x3
[=] 	fstat(fd = 0x3, buf_ptr = 0x7ff3c128) = 0x0
[=] 	mmap(addr = 0x0, length = 0x1000, prot = 0x3, flags = 0x4000802, fd = 0xffffffff, pgoffset = 0x0) = 0x90001000
[=] 	read(fd = 0x3, buf = 0x90001000, length = 0x1000) = 0x1000
[=] 	mmap(addr = 0x0, length = 0x3f000, prot = 0x0, flags = 0x802, fd = 0xffffffff, pgoffset = 0x0) = 0x90002000
[=] 	mmap(addr = 0x90002000, length = 0x2e184, prot = 0x5, flags = 0x12, fd = 0x3, pgoffset = 0x0) = 0x90002000
[=] 	mmap(addr = 0x90040000, length = 0x2dc, prot = 0x3, flags = 0x12, fd = 0x3, pgoffset = 0x2e000) = 0x90040000
[=] 	close(fd = 0x3) = 0x0
[=] 	munmap(addr = 0x90001000, length = 0x1000) = 0x0
[=] 	open(filename = 0x7ff3c2b0, flags = 0x0, mode = 0x0) = 0x3
[=] 	fstat(fd = 0x3, buf_ptr = 0x7ff3c118) = 0x0
[=] 	mmap(addr = 0x0, length = 0x1000, prot = 0x3, flags = 0x4000802, fd = 0xffffffff, pgoffset = 0x0) = 0x90001000
[=] 	read(fd = 0x3, buf = 0x90001000, length = 0x1000) = 0x1000
[=] 	mmap(addr = 0x0, length = 0x14f000, prot = 0x0, flags = 0x802, fd = 0xffffffff, pgoffset = 0x0) = 0x90041000
[=] 	mmap(addr = 0x90041000, length = 0x129ac4, prot = 0x5, flags = 0x12, fd = 0x3, pgoffset = 0x0) = 0x90041000
[=] 	mmap(addr = 0x9017b000, length = 0x13bcc, prot = 0x3, flags = 0x12, fd = 0x3, pgoffset = 0x12a000) = 0x9017b000
[=] 	mmap(addr = 0x9018f000, length = 0xfb8, prot = 0x3, flags = 0x812, fd = 0xffffffff, pgoffset = 0x0) = 0x9018f000
[=] 	close(fd = 0x3) = 0x0
[=] 	munmap(addr = 0x90001000, length = 0x1000) = 0x0
[=] 	open(filename = 0x7ff3c2a0, flags = 0x0, mode = 0x0) = 0x3
[=] 	fstat(fd = 0x3, buf_ptr = 0x7ff3c108) = 0x0
[=] 	mmap(addr = 0x0, length = 0x1000, prot = 0x3, flags = 0x4000802, fd = 0xffffffff, pgoffset = 0x0) = 0x90001000
[=] 	read(fd = 0x3, buf = 0x90001000, length = 0x1000) = 0x1000
[=] 	mmap(addr = 0x0, length = 0x24000, prot = 0x0, flags = 0x802, fd = 0xffffffff, pgoffset = 0x0) = 0x90190000
[=] 	mmap(addr = 0x90190000, length = 0xc768, prot = 0x5, flags = 0x12, fd = 0x3, pgoffset = 0x0) = 0x90190000
[=] 	mmap(addr = 0x901ac000, length = 0x563c, prot = 0x3, flags = 0x12, fd = 0x3, pgoffset = 0xc000) = 0x901ac000
[=] 	mmap(addr = 0x901b2000, length = 0x1940, prot = 0x3, flags = 0x812, fd = 0xffffffff, pgoffset = 0x0) = 0x901b2000
[=] 	close(fd = 0x3) = 0x0
[=] 	munmap(addr = 0x90001000, length = 0x1000) = 0x0
[=] 	open(filename = 0x7ff3c290, flags = 0x0, mode = 0x0) = 0x3
[=] 	fstat(fd = 0x3, buf_ptr = 0x7ff3c0f8) = 0x0
[=] 	mmap(addr = 0x0, length = 0x1000, prot = 0x3, flags = 0x4000802, fd = 0xffffffff, pgoffset = 0x0) = 0x90001000
[=] 	read(fd = 0x3, buf = 0x90001000, length = 0x1000) = 0x1000
[=] 	mmap(addr = 0x0, length = 0x27000, prot = 0x0, flags = 0x802, fd = 0xffffffff, pgoffset = 0x0) = 0x901b4000
[=] 	mmap(addr = 0x901b4000, length = 0x162f0, prot = 0x5, flags = 0x12, fd = 0x3, pgoffset = 0x0) = 0x901b4000
[=] 	mmap(addr = 0x901da000, length = 0x45c, prot = 0x3, flags = 0x12, fd = 0x3, pgoffset = 0x16000) = 0x901da000
[=] 	close(fd = 0x3) = 0x0
[=] 	munmap(addr = 0x90001000, length = 0x1000) = 0x0
[=] 	open(filename = 0x7ff3c280, flags = 0x0, mode = 0x0) = 0x3
[=] 	fstat(fd = 0x3, buf_ptr = 0x7ff3c0e8) = 0x0
[=] 	mmap(addr = 0x0, length = 0x1000, prot = 0x3, flags = 0x4000802, fd = 0xffffffff, pgoffset = 0x0) = 0x90001000
[=] 	read(fd = 0x3, buf = 0x90001000, length = 0x1000) = 0x1000
[=] 	mmap(addr = 0x0, length = 0x88000, prot = 0x0, flags = 0x802, fd = 0xffffffff, pgoffset = 0x0) = 0x901db000
[=] 	mmap(addr = 0x901db000, length = 0x707b0, prot = 0x5, flags = 0x12, fd = 0x3, pgoffset = 0x0) = 0x901db000
[=] 	mmap(addr = 0x9025b000, length = 0x1ef0, prot = 0x3, flags = 0x12, fd = 0x3, pgoffset = 0x70000) = 0x9025b000
[=] 	mmap(addr = 0x9025d000, length = 0x5440, prot = 0x3, flags = 0x812, fd = 0xffffffff, pgoffset = 0x0) = 0x9025d000
[=] 	close(fd = 0x3) = 0x0
[=] 	munmap(addr = 0x90001000, length = 0x1000) = 0x0
[=] 	open(filename = 0x7ff3c270, flags = 0x0, mode = 0x0) = 0x3
[=] 	fstat(fd = 0x3, buf_ptr = 0x7ff3c0d8) = 0x0
[=] 	close(fd = 0x3) = 0x0
[=] 	open(filename = 0x7ff3c260, flags = 0x0, mode = 0x0) = 0x3
[=] 	fstat(fd = 0x3, buf_ptr = 0x7ff3c0c8) = 0x0
[=] 	close(fd = 0x3) = 0x0
[=] 	open(filename = 0x7ff3c250, flags = 0x0, mode = 0x0) = 0x3
[=] 	fstat(fd = 0x3, buf_ptr = 0x7ff3c0b8) = 0x0
[=] 	close(fd = 0x3) = 0x0
[=] 	open(filename = 0x7ff3c240, flags = 0x0, mode = 0x0) = 0x3
[=] 	fstat(fd = 0x3, buf_ptr = 0x7ff3c0a8) = 0x0
[=] 	close(fd = 0x3) = 0x0
[=] 	open(filename = 0x7ff3c230, flags = 0x0, mode = 0x0) = 0x3
[=] 	fstat(fd = 0x3, buf_ptr = 0x7ff3c098) = 0x0
[=] 	close(fd = 0x3) = 0x0
[=] 	stat(path = 0x90000878, buf_ptr = 0x7ff3ca20) = 0x0
[=] 	open(filename = 0x7ff3c210, flags = 0x0, mode = 0x0) = 0x3
[=] 	fstat(fd = 0x3, buf_ptr = 0x7ff3c078) = 0x0
[=] 	close(fd = 0x3) = 0x0
[=] 	open(filename = 0x47bf340, flags = 0x0, mode = 0x0) = 0x3
[=] 	read(fd = 0x3, buf = 0x7ff3cb90, length = 0x4) = 0x4
[=] 	close(fd = 0x3) = 0x0
[=] 	mprotect(start = 0x901ac000, mlen = 0x1000, prot = 0x1) = 0x0
[=] 	mprotect(start = 0x9025b000, mlen = 0x1000, prot = 0x1) = 0x0
[=] 	mprotect(start = 0x47cf000, mlen = 0x1000, prot = 0x1) = 0x0
[=] 	ioctl(fd = 0x0, cmd = 0x540d, arg = 0x7ff3c9e8) = -0x1 (EPERM)
[=] 	ioctl(fd = 0x1, cmd = 0x540d, arg = 0x7ff3c9e8) = -0x1 (EPERM)
[=] 	getpid() = 0x512
[=] 	getrlimit(res = 0x3, rlim = 0x7ff3ca18) = 0x0
[=] 	rt_sigaction(signum = 0x20, act = 0x7ff3ca30, oldact = 0x0) = 0x0
[=] 	rt_sigaction(signum = 0x21, act = 0x7ff3ca30, oldact = 0x0) = 0x0
[=] 	rt_sigaction(signum = 0x22, act = 0x7ff3ca30, oldact = 0x0) = 0x0
[=] 	rt_sigprocmask(how = 0x1, nset = 0x7ff3ca20, oset = 0x0, sigsetsize = 0x10) = 0x0
[=] 	rt_sigprocmask(how = 0x2, nset = 0x7ff3ca20, oset = 0x0, sigsetsize = 0x10) = 0x0
[=] 	brk(inp = 0x0) = 0x4c3000
[=] 	brk(inp = 0x4c4000) = 0x4c4000
[=] 	brk(inp = 0x4d3000) = 0x4d3000
[=] 	rt_sigaction(signum = 0x12, act = 0x7ff3ce24, oldact = 0x7ff3ce08) = 0x0
[=] 	rt_sigaction(signum = 0x10, act = 0x7ff3ce24, oldact = 0x7ff3ce08) = 0x0
[=] 	ipc(call = 0x17, first = 0x1857, second = 0x2d, third = 0x200, ptr = 0x0, fifth = 0x0) = 0xf000000
[=] 	ipc(call = 0x15, first = 0xf000000, second = 0x0, third = 0x7ff3ce10, ptr = 0x0, fifth = 0x0) = -0x1 (EPERM)
[=] 	ipc(call = 0x17, first = 0x1860, second = 0x901, third = 0x200, ptr = 0x0, fifth = 0x0) = 0xf001000
[=] 	ipc(call = 0x15, first = 0xf001000, second = 0x0, third = 0x7ff3ce10, ptr = 0x0, fifth = 0x0) = -0x1 (EPERM)
[=] 	ipc(call = 0x17, first = 0x6661, second = 0x180000, third = 0x200, ptr = 0x0, fifth = 0x0) = 0xf002000
[=] 	open(filename = 0x9015df74, flags = 0x2102, mode = 0x180) = 0x3
[=] 	ipc(call = 0x15, first = 0xf002000, second = 0x0, third = 0x7ff3ce10, ptr = 0x50000000, fifth = 0x0) = -0x1 (EPERM)
[=] 	close(fd = 0x3) = 0x0
[=] 	exit_group(code = 0xffffffff) = ?

@elicn
Copy link
Member

elicn commented Apr 23, 2023

Yes, but this is only part of the log which is pasted directly and not as text file.
Please upload the full log as an attachment.

@SRSG
Copy link
Author

SRSG commented Apr 23, 2023

I'm not sure.These are all it pasted.I just code like this:

ql = Qiling(path, rootf, verbose = QL_VERBOSE.DEFAULT)
ql.run()

Can you show me how to make it?Maybe the code to get the full log you want.Then I'll upload the file.

@elicn
Copy link
Member

elicn commented Apr 23, 2023

You may pipe both stodout and stderr into a file, e.g.:

python3 ./my_qiling_script.py 2>&1 | tee out.log

Then upload out.log here. Just make sure the logs do not contain personal or other kind of sensitive data you don't want to share.

If you could include the following hook in your code before you run it, that would be helpful:

from qiling.const import QL_INTERCEPT

def __onenter_ipc(ql: Qiling, call: int, first: int, second: int, third: int, ptr: int, fifth: int):
    if call == 21:
        ql.log.info(f'about to call shmat: ptr ({ptr:#010x}) is {"" if ql.mem.is_available(ptr, ql.arch.pointersize) else "not "}available')

ql.os.set_syscall('ipc', __onenter_ipc, QL_INTERCEPT.ENTER)

@SRSG
Copy link
Author

SRSG commented Apr 25, 2023

out.log
I add your hook and upload the log file.

@elicn
Copy link
Member

elicn commented Apr 27, 2023

OK, I think I've found the root cause:
Line 95 in the attached log says: ipc(call = 0x17, first = 0x6661, second = 0x180000, third = 0x200, ptr = 0x0, fifth = 0x0)
Which correponds to: shmget(key=0x6661, size=0x180000, shmflg=IPC_CREAT)

However, note that the shmflg should also include the creation mode, which implied here to be 0000 (instead of, for example, 0644). This is why the following call to shmat at line 98 fails: by default all segments are attached with a read permission. If the requestor doesn't have it (and it doesn't, because of the 0000), then an error is returned.

I searched both man pages and Linux kernel implementation, and did not see any reference to a read permission given by default on shmget. All code examples I saw specified the creating mode explicitly, ex:

shmid = shmget (key, size, IPC_CREAT | 0660);

If you know something different, then let me know. Otherwise, the existing Qiling implementation seems to be accordig to spec.

For reference:
From shmget man page:

When a new shared memory segment is created, its contents are initialized to zero values, and its associated data structure, shmid_ds (see shmctl(2)), is initialized as follows:
shm_perm.cuid and shm_perm.uid are set to the effective user ID of the calling process.
shm_perm.cgid and shm_perm.gid are set to the effective group ID of the calling process.
The least significant 9 bits of shm_perm.mode are set to the least significant 9 bit of shmflg. <--- HERE
shm_segsz is set to the value of size.
shm_lpid, shm_nattch, shm_atime and shm_dtime are set to 0.
shm_ctime is set to the current time.

From shmat man page:

If SHM_RDONLY is specified in shmflg, the segment is attached for reading and the process must have read permission for the segment. Otherwise the segment is attached for read and write and the process must have read and write permission for the segment. There is no notion of a write-only shared memory segment.

@SRSG
Copy link
Author

SRSG commented Apr 27, 2023

OK,I think I have understood what you found.I check some reference again and get that maybe the existing Qiling_shm is right in deed.

But I think we need determine if your first change(3 weeks ago because at that time shmat didn't return error ) considered this shmflg(0x200) or the permisson.If this difference arises because you changed something about it, then there should be no problem to current Qiling_shm.And I would attribute this error to a program exception.

@elicn
Copy link
Member

elicn commented Apr 27, 2023

The older implementation followed the spec to some extent.
On each iteration we had here I got the implementation closer and closer to spec, in order to eliminate bugs that comes from spec misalignments.

I created a test program that uses shmget and shmat, and Qiling pretty much follows the same flow shown in strace. When I specify a mode of 0664 the shm gets attached, and when I use just IPC_CREAT without mode, it gets rejected with a "permission denied" error. Question is whether the target architecture you're emulating (MIPS O32?) has different assumptions / defaults; I haven't found evidence for that anyway.

@SRSG
Copy link
Author

SRSG commented Apr 28, 2023

Yes,it's a service program in a certain firmware for mips32.I think your viewpoint is reasonable.However,I didn't see anything about it yet in my reverse process.It's sad that I cannot figure it out either.

@elicn
Copy link
Member

elicn commented Apr 29, 2023

Let me know if you want to investigate it further by providing more use cases and code examples, or close the issue.

@elicn elicn closed this as completed May 29, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants