This section written by Hans Lermen <lermen@fgan.de> , Apr 6, 1997. And updated by Eric Biederman <ebiederm+eric@npwt.net> 30 Nov 1997.
Well, I got sick with the complaints about 'have problems running as user' and did much effort to 'learn' about what we were doing with priv-settings. To make it short: It was a real mess, there was the whole dosemu history of different strategies to reach the same aim. Well, this didn't make it clearer. I appreciate all the efforts that were done by a lot of people in the team to make that damn stuff working, and atleast finding all those places where we need to handle privs was a great work and is yet worth. ... but sorry, how it was handled didn't work.
The main odds happened because sometimes functions, that were called within a priv_on/off()...priv_default() brackets were calling priv_on/off()...priv_default() themselves. And with introduction of this priv_default() macro, which handled the 'default' case (run as user or run as root) the confusion was complete. Conclusion: recursive settings were not handled, and you always had to manually keep track of wether privs were on or off. ... terrible and not maintainable.
Therefore I decided to do it 'the right way (tm)' and overworked it completely. The only thing that now remains to do, is to check for more places where we have to temporary allow/disallow root priviledges. I also decided to be a good boy and make 'RUN_AS_ROOT' a compile time option and 'run as user' the default one.
(promise: I'll also will run dosemu as user before releasing, so that I can see, if something goes wrong with it ;-)
Enter Eric:
What we have been suffering from lately is that threads were added to dosemu, and the stacks Hans added when he did it 'the right way (tm)' were all of a sudden large static variables that could not be kept consistent. That and hans added caching of the curent uids for efficiency in dosemu, again more static variables.
When I went through the first time and added all of the strange unmaintainable things Hans complains of above, and found where priveleges where actually needed I hand't thought it was as bad as Hans perceived it, so I had taken the lazy way out. That and my main concern was to make the privelege setting consistent enough to not give mysterous erros in dosemu. But I had comtemplated doing it 'the right way (tm)' and my scheme for doing it was a bit different from the way Hans did it.
With Hans the stack was explicit but hidden behind the scenes. With my latest incarnation the stack is even more explicit. The elements of the privelege stack are now local variables in subroutines. And these local variables need to be declared explicitly in a given subroutine. This method isn't quite a fool proof as Han's method, but a fool could mess up Hans's method up as well. And any competent person should be able to handle a local variable, safely.
For the case of the static cached uid I have simply placed them in the thread control block. The real challenge in integrating the with the thread code is the thread code was using root priveleges and changing it's priveleges with the priv code during it's initialization. For the time being I have disabled all of the thread code's mucking with root priveleges, and placed it's initialization before the privelege code's initialization. We can see later if I can make Han's thread code work `the right way (tm)' as he did for my privelege code.
ReEnter Hans: ;-)
In order to have more checking wether we (not an anonymous fool) are forgetting something on the road, I modified Erics method such that the local variable is declared by a macro and preset with a magic, that is checked in priv.c. The below explanations reflect this change.
ReReEnter Hans (2000/02/28): ;-)
The treads code has definitively gone (no one made serious use of it), so the above thread related arguments too.
This works as follows
All settings have to be done in 'pairs' such as
enter_priv_on(); /* need to have root access for 'do_something' */ do_something(); leave_priv_setting(); |
enter_priv_off(); /* need pure user access for 'do_something' */ do_something(); leave_priv_setting(); |
On enter_priv_XXX() the current state will be saved (pushed) on a local variable on the stack and later restored from that on leave_priv_setting(). This variable is has to be defined at entry of each function (or block), that uses a enter/leave_priv bracket. To avoid errors it has to be defined via the macro PRIV_SAVE_AREA. The 'stack depth' is just _one_ and is checked to not overflow. The enter/leave_priv_* in fact are macros, that pass a pointer to the local privs save area to the appropriate 'real_' functions. ( this way, we can't forget to include priv.h and PRIV_SAVE_AREA ) Hence, you never again have to worry about previous priv settings, and whenever you feel you need to switch off or on privs, you can do it without coming into trouble.
We now have the system calls (getuid, setreuid, etc.) only in src/base/misc/priv.c. We cash the setting and don't do unnecessary systemcalls. Hence NEVER call 'getuid', 'setreuid' etc. yourself, instead use the above supplied functions. The only places where I broke this 'holy law' myself was when printing the log, showing both values (the real and the cached one).
In case of dosemu was started out of a root login, we skip all priv-settings. There is a new variable 'under_root_login' which is only set when dosemu is started from a root login.
On all places were iopl() is called outside a enter_priv...leave_priv bracket, the new priv_iopl() function is to be used in order to force privileges.
This is much cleaner and 'automagically' also solves the problem with the old 'priv_off/default' sheme. Because, 'running as user' just needs to do _one_ priv_off at start of dosemu, if we don't do it we are 'running as root' ;-)
A last remark: Though it may be desirable to have a non suid root dosemu, this is not possible. Even if we get GGI in the official kernel, we can't solve the problems with port access (which we need for a lot of DOS apps) ... and ioperm() / iopl() need root privileges. This leads to the fact that 'i_am_root' will be always '1', makeing it a macro gives GCC a changes optimize away the checks for it. We will leave 'i_am_root' in, maybe there is some day in the future that gives us a kernel allowing a ports stuff without root privilege, ... never say never ;-)
Enter Eric:
The current goal is to have a non suid-root dosemu atleast in X, lacking some features. But the reason I disabled it when I first introduced the infamous in this file priv_on/priv_default mechanism is that it hasn't been tested yet, still stands. What remains to do is an audit of what code _needs_ root permissions, what code could benefit with a sgid dosemu, and what code just uses root permissions because when we are suid root some operations can only been done as root.
When the audit is done (which has started with my most recent patch (I now know what code to look at)). It should be possible to disable options that are only possible when we are suid root, at dosemu configuration time. Then will be the task of finding ways to do things without root permissions.
Enter Hans (960626):
Last act of this story: DOSEMU now can run non-suid root and though it looses a lot of features because of not beeing able to run on console in this mode or not able to do any ports stuff e.t.c., its quite useable under X (I was even able to run win31, hence DPMI is secure now with the s-bit off).
The names of some variables where changed, such that it conforms more to what they now do: i_am_root became can_do_root_stuff, and under_root_login was splitted into the static skip_priv_setting, while the global valiable under_root_login keeps its meaning.
skip_priv_setting is set for both: running as root and running as user non-suid root. can_do_root_stuff is global and used in the same places were i_am_root was used before.
On start of DOSEMU, we now call parse_dosemu_users() directly after priv_init(), and this is top of main(). This way we catch any hacker attack quite early and should be much securer as ever before.
Additionally, parse_dosemu_users now checks for the keyword `nosuidroot' in /etc/dosemu.users and if this is found for a user, this user is not allowed to execute DOSEMU on a suid root binary (though he can use a non-suid root copy of it) and dosemu will exit in this case. Dropping all priviledges on a suid root binary and continue (as if the -s bit wasn't set) is not possible, because /proc/self/mem has the ownership of the suid binary (hence root) and dosemu would crash later, when trying to open it. Do a chown on /proc/self/mem doesn't work either.
The last action to make the non-suid root stuff working, was moving the files from /var/run/* to some user accessable location. This now is ~/.dosemu/* and dosdebug was adapted to support that.