Skip to contentSkip to conversation

The disappearing sys_call_table export.

DISCUSSION Chuck Ebbert 9 participants 17 messages May 8, 2003 – May 11, 2003

lore
Share & tools
lore
#0 lore raw
Quoted 7 lines · Al Viro
Quoted 4 lines
Quoted 2 lines

I'd make a stab at it if I knew that it stood a chance of getting accepted.

I dont think it has.

I think it could, actually - who maintains fortunes these days? It's a bit too long, though...

Wow, Advanced Sarcasm. Must be part of the Graduate program...

Meanwhile on Win2k I can intercept any IO request by
wrting a filter driver, and that driver can get control on the way back to userspace by registering a completion routine. Such filters can be arbitrarily chained together and can be placed either above or below an FSD, making such things as virus detection, HSM and disk mirroring much easier to write...

How would I do this on Linux? How would virus detection and HSM
coexist? (HSM would have to be 'above' the virus detector, since it makes no sense to try and scan a file that's been migrated until it gets recalled back to disk.)

#1 lore raw
Quoted 2 lines · Chuck Ebbert

Meanwhile on Win2k I can intercept any IO request by
wrting a filter driver,

you can write a stackable filesystem on linux, too and intercept any I/O request. You just have to do it through a sane interface, mount and not by patching the syscall table - which you can do under windows either. (at least not as part of the public API).

#2 lore raw
In reply to Chuck Ebbert
Quoted 9 lines · Chuck Ebbert
Quoted 7 lines · Al Viro
Quoted 4 lines
Quoted 2 lines

I'd make a stab at it if I knew that it stood a chance of getting accepted.

I dont think it has.

I think it could, actually - who maintains fortunes these days? It's a bit too long, though...

Quoted 8 lines

Wow, Advanced Sarcasm. Must be part of the Graduate program...

Meanwhile on Win2k I can intercept any IO request by
wrting a filter driver, and that driver can get control on the way back to userspace by registering a completion routine. Such filters can be arbitrarily chained together and can be placed either above or below an FSD, making such things as virus detection, HSM and disk mirroring much easier to write...

note the key word in the phrase "filter DRIVER". Linux modules can intercep any I/O directed toward them. and the filesystem layer can intercept any filesystem call. And there are filesystem modules.

M$ seems to treat everything as a disk file (even "pipes" are implemented as temporary files).

Have you tried catching the display IO ???

HSM has existed on UNIX based machines for a long time.

Quoted 4 lines

How would I do this on Linux? How would virus detection and HSM
coexist? (HSM would have to be 'above' the virus detector, since it makes no sense to try and scan a file that's been migrated until it gets recalled back to disk.)

I would expect the same way the NFS module interceps file system calls.

There is NO reason a custom filesystem cannot be layered over other filesystems. It might not be done today (though the references to "userfs" keep showing up in such discussions).

I do question the validity of virus detection though. Once examined, fix the vulnerability. No more virus.

Virus detection can never be completely done. And it imposes a constantly increasing overhead since you must be able to identify all pre-existing viruses. This list of "pre-existing" will be constantly growing.

Fix the vulnerability. Then there won't be a virus.

#3 lore raw
Quoted 2 lines

M$ seems to treat everything as a disk file (even "pipes" are implemented as temporary files).

So did original Unix, it was a disk file that was anonymous and just used the direct pointers to blocks for a ring buffer. Storing pipe data in RAM in the old days was a hideous waste of resources.

Quoted 3 lines

There is NO reason a custom filesystem cannot be layered over other filesystems. It might not be done today (though the references to "userfs" keep showing up in such discussions).

Erez Zadoz (not sure of the spelling) did some stacking fs modules on Linux

Quoted 1 line

Fix the vulnerability. Then there won't be a virus.

But you don't know if its fixed and if there are any more holes without being able to detect attackers be they electronic or human.

#4 lore raw

Good day, all,

Quoted 6 lines · Alan Cox
Quoted 3 lines

There is NO reason a custom filesystem cannot be layered over other filesystems. It might not be done today (though the references to "userfs" keep showing up in such discussions).

Erez Zadoz (not sure of the spelling) did some stacking fs modules on Linux

Erez Zadok maintains the FiST (File System Translator) project at
http://www1.cs.columbia.edu/~ezk/research/fist/ . For those not familiar with the project, one writes an upper level filesystem that can modify VFS requests or results, providing a VFS proxy.
Cheers,
- Bill

---------------------------------------------------------------------------
"All programs evolve until they can send email."
-- Richard Letts
"Except Microsoft Exchange."
-- Art
(found on the Snort web site)
--------------------------------------------------------------------------
William Stearns ([email protected]). Mason, Buildkernel, freedups, p0f,
rsync-backup, ssh-keyinstall, dns-check, more at: http://www.stearns.org
Linux articles at: http://www.opensourcedigest.com
--------------------------------------------------------------------------

#5 lore raw
In reply to Alan Cox

On Thursday 08 May 2003 10:22, Alan Cox wrote:
[snip]

Quoted 4 lines
Quoted 1 line

Fix the vulnerability. Then there won't be a virus.

But you don't know if its fixed and if there are any more holes without being able to detect attackers be they electronic or human.

Detecting attackers is a different situation. An attack that is already fixed is not a serious problem other than bandwidth. Virus scanners can't do that anyway - they can only detect what has already been detected... and which should have been fixed by the time the signature could have been put out, anyway. Detection should be part of an intrusion facility (isn't LIDS supposed to do that?)

Second, I want to setup SELinux to sandbox various facilities anyway (delayed due to job change). That should isolate any unknown attack to just one service, and protect the overall system.

#6 lore raw
In reply to Alan Cox

Hello every one:

This isn't really a reply to Alan Cox, i am just adding my own two bits worth to the debate.

I would like to say that sys_call_table export should not disappear, as overriding system calls on-the-fly through loadable modules does have some very practical applications. This is despite the fact that one has to write a slightly "ugly cpu-specific glue" to make it happen :-)

Case in point, I wrote a security module for Linux that overrides _all_ 237 systemcalls to audit and control the use of the system calls on a per uid basis. (i.e. if the user was actually allowed to make the system call or not) and return -EPERM or jump to system call proper.

Not sure whether anyone would be interested in details of my implementation, so i won't get into it but there are a few things which may be of interest from the perspective of the esthetical issues:

1. All 237 system calls are succesfully overriden audited and controlled through precisely one overriding function (there aren't 237 calls replacing originals in my implementation).

2. The parameters passed to the calls are analysable.

3. The return values from system call proper as well as any values returned on stace are analysable.

4. The module can be safely loaded and unloaded, as well as functionality restored because it is (believe it or not :-) possible to track usage.

Now as to why do it as a module, rather than patching the kernel? Well there are several good reasons, but the most obvious is the reason why modules were created in the first place: Namely, that new functionality can be added to a system without having to shut it down, reboot it, or for that matter interrupt services.

Specifically this module saved a number of systems from ptrace denial of service attack by simply disallowing ptrace to "untrusted" users on the systems without any fuss or muss. (There are other more interesting and exotic uses).

I would also like to say that Linux modules should not be limited to just device drivers, even though the module infrastructure may have been originally conceived that way. The beauty of Linux modules, combined with the monolithic kernel approach is their ability to expand the kernel on the fly. Heck, who knows, there may be a day when we can simply swap an entirely brand new kernel into place and simply continue from where the previous kernel left off.

IMHO, the question really boils down to this: "What is a good reason to elimnate this ability, when it obviously, provides useful functionality with some care?"

Saying that kernel programmers are not careful enough to "use it correctly" is a bit condesending. Removing it for esthetics or for an obscure notion of "good programming practices" is not a reasonable enough argument either since we've definitely used a monolithic kernel design at the largest scale of the spectrum and gotos at the smallest end, because we favour practicality vs. beauty, hands down, so what gives here?

Now, if the maintainers (esp. Linus or Alan) simply wanna do it "just 'cause", who can really argue with them? (well, I might a bit :-).

Cheers,

Ahmed Masud.

#7 lore raw
Quoted 4 lines · Ahmed Masud

Case in point, I wrote a security module for Linux that overrides _all_ 237 systemcalls to audit and control the use of the system calls on a per uid basis. (i.e. if the user was actually allowed to make the system call or not) and return -EPERM or jump to system call proper.

I'm pretty sure that auditing by your module can easily be avoided.

examle: pseudocode for the unlink syscall

long your_wrapped_syscall(char *userfilename)

{
    char kernelpointer[something];
    copy_from_user(kernelpointer, usefilename, ...);
    audit_log(kernelpointer);
    return original_syscall(userfilename);
}

now.... the original syscall does ANOTHER copy_from_user(). Eg I can easily fool your logging by having a second thread change the filename between the time your code copies it and the time the original syscall copies it again. The chances of getting the timing right are 50% at least (been there done that ;)

The only solution for this is to check/audit/log things after the ONE copy. Eg not by overriding the syscall but inside the syscall.

#8 lore raw

Hi Arjan,

Quoted 19 lines · Arjan van de Ven
Quoted 4 lines · Ahmed Masud

Case in point, I wrote a security module for Linux that overrides _all_ 237 systemcalls to audit and control the use of the system calls on a per uid basis. (i.e. if the user was actually allowed to make the system call or not) and return -EPERM or jump to system call proper.

I'm pretty sure that auditing by your module can easily be avoided.

examle: pseudocode for the unlink syscall

long your_wrapped_syscall(char *userfilename)

{
    char kernelpointer[something];
    copy_from_user(kernelpointer, usefilename, ...);
    audit_log(kernelpointer);
    return original_syscall(userfilename);
}
Quoted 5 lines

now.... the original syscall does ANOTHER copy_from_user(). Eg I can easily fool your logging by having a second thread change the filename between the time your code copies it and the time the original syscall copies it again. The chances of getting the timing right are 50% at least (been there done that ;)

You are right, if operations occur in the sequence above.

What are your thoughts on the following two
A)

	long wrapper_call(args) {
		yield(random(threshold)); /* yeild is a sleep */
		rv = orig_syscall(args...);
		copy_from_user(audit_data,args...);
		audit_log(audit_data);
		return rv;
	}

That becomes a bit more difficult to time, because the attacker doesn't know when the system call will actually perform its own copy_from_user vs. return vs. the audit's copy_from_user, If the correct upper threshold for each system call is picked timing attacks can be made alot harder.

B)

	long wrapper_call(args) {
		yield(random(threshold));
		copy_from_user(audit_data1,args...);
		rv = orig_syscall(args...);
		yield(random(threshold));
		copy_from_user(audit_data2,args...);
		audit_log(audit_data1);
		audit_log(audit_data2);
		return rv;
	}

Suspicious data analysis is then performed by a user-land tool to further ensure integrity.

I would just like to say that above does not pretend to be speed happy, still for practical purposes you can assume that the yield is a lot shorter (a couple of orders of magnitude) than the duration of the system call.

Cheers,

Ahmed.

#9 lore raw
Quoted 10 lines · Ahmed Masud

What are your thoughts on the following two
A)

	long wrapper_call(args) {
		yield(random(threshold)); /* yeild is a sleep */
		rv = orig_syscall(args...);
		copy_from_user(audit_data,args...);
		audit_log(audit_data);
		return rv;
	}
Quoted 4 lines

That becomes a bit more difficult to time, because the attacker doesn't know when the system call will actually perform its own copy_from_user vs. return vs. the audit's copy_from_user, If the correct upper threshold for each system call is picked timing attacks can be made alot harder.

no it's not. just make sure the page with the filename is paged out, and use mincore to poll for the pagefault ;)
And with unlink you can observe the results as well (think dnotify) so you can intervene before the second audit copy

Quoted 13 lines

B)

	long wrapper_call(args) {
		yield(random(threshold));
		copy_from_user(audit_data1,args...);
		rv = orig_syscall(args...);
		yield(random(threshold));
		copy_from_user(audit_data2,args...);
		audit_log(audit_data1);
		audit_log(audit_data2);
		return rv;
	}
Quoted 2 lines

Suspicious data analysis is then performed by a user-land tool to further ensure integrity.

still not secure, now you copy 3 times so all I need to do is flip data twice ;)

#10 lore raw
Quoted 6 lines · Arjan van de Ven
Quoted 4 lines · Ahmed Masud

That becomes a bit more difficult to time, because the attacker doesn't know when the system call will actually perform its own copy_from_user vs. return vs. the audit's copy_from_user, If the correct upper threshold for each system call is picked timing attacks can be made alot harder.

Quoted 8 lines

no it's not. just make sure the page with the filename is paged out, and use mincore to poll for the pagefault ;)
And with unlink you can observe the results as well (think dnotify) so you can intervene before the second audit copy

still not secure, now you copy 3 times so all I need to do is flip data twice ;)

Very interesting indeed, good thing i am not auditing parameters ;) hehe. The only thing i was tracking was whether the particular system call was allowed or denied to the user, due to an ACL and because that doesn't rely on the user-land data i am not particulary effected.

I will setup some parametric auditing on pointer data and attack the environment using your technique above to see if something can be done about it.

(heheh there goes the afternoon!)

Cheers,

Ahmed.

#11 lore raw
In reply to Arjan van de Ven

Hello again Arjan,

Further to the previous reply, a user can simply be denied mincore :) so no valid information would come to the attacker. I know that there is no such problem within the allow/deny code so the attack threat can be minimized further.

Ahmed.

#12 lore raw
In reply to Ahmed Masud
Quoted 1 line · Ahmed Masud

yield(random(threshold)); /* yeild is a sleep */

[...]

Quoted 2 lines

That becomes a bit more difficult to time, because the attacker doesn't know when the system call will actually perform its own copy_from_user vs.

So the probability of getting through in one try is about (tR+tH)/tH, where tR is the average random delay, and tH is the time between the check and the actual access.

If you keep on trying until you get through, you'll succeed on average after tR^2/tH+tR.

If you make tR = 1 s (that's pretty long, e.g. if you do this to unlink(2), a rm -rf of the kernel source tree would take about four hours) and assume that tH is only one microsecond, the race condition can still be exploited within typically less than one fortnight.

Since the system would be idle most of the time, such a brute-force attack could easily go unnoticed, even if somebody cares to monitor the system often enough.

Sounds like voodoo security to me.

- Werner

Signature · 3 lines
  _________________________________________________________________________
 / Werner Almesberger, Buenos Aires, Argentina         [email protected] /
/_http://www.almesberger.net/____________________________________________/
#13 lore raw
In reply to Arjan van de Ven
Quoted 2 lines · Arjan van de Ven

The only solution for this is to check/audit/log things after the ONE copy. Eg not by overriding the syscall but inside the syscall.

Well, not exactly the _only_ one. Inferior alternatives include

- tracking all user memory accesses, and protecting those pages against modifications by other processes (but this adds a deadlock risk)

- like above, but copy all such data to/from a user space area that is then protected

- like above, but use kernel space instead, then use set_fs(KERNEL_DS)

Which probably just proves that there are always more painful ways to do things :-)

- Werner

Signature · 3 lines
  _________________________________________________________________________
 / Werner Almesberger, Buenos Aires, Argentina         [email protected] /
/_http://www.almesberger.net/____________________________________________/
#14 lore raw
In reply to Arjan van de Ven

[Arjan van de Ven]

Quoted 18 lines · Ahmed Masud
Quoted 4 lines

Case in point, I wrote a security module for Linux that overrides _all_ 237 systemcalls to audit and control the use of the system calls on a per uid basis. (i.e. if the user was actually allowed to make the system call or not) and return -EPERM or jump to system call proper.

I'm pretty sure that auditing by your module can easily be avoided.

examle: pseudocode for the unlink syscall

long your_wrapped_syscall(char *userfilename)

{
    char kernelpointer[something];
    copy_from_user(kernelpointer, usefilename, ...);
    audit_log(kernelpointer);
    return original_syscall(userfilename);
}
Quoted 8 lines

now.... the original syscall does ANOTHER copy_from_user(). Eg I can easily fool your logging by having a second thread change the filename between the time your code copies it and the time the original syscall copies it again. The chances of getting the timing right are 50% at least (been there done that ;)

The only solution for this is to check/audit/log things after the ONE copy. Eg not by overriding the syscall but inside the syscall.

just replace

     return original_syscall(userfilename);
with
     return original_syscall(kernelpointer);
Signature · 2 lines
 - Terje
[email protected]
#15 lore raw
Quoted 6 lines · Terje Malmedal

just replace

     return original_syscall(userfilename);
with
     return original_syscall(kernelpointer);

That's about right :) need to switch to kernel space with set_fs though (see other messages in the thread). - A

#16 lore raw
In reply to Chuck Ebbert
Quoted 4 lines · Chuck Ebbert

How would I do this on Linux? How would virus detection and HSM
coexist? (HSM would have to be 'above' the virus detector, since it makes no sense to try and scan a file that's been migrated until it gets recalled back to disk.)

Userspace

	--- ptrace

VFS
Loadable file system module (which can be made to stack stuff)
Block Layer
Loadable disk driver (Which can be made to stack)
Disk