Question

Original Question

I'm trying to create a Lisp library that can, among other things, edit my system's /etc/hosts file and nginx configurations. The problem I'm facing is that, because my Lisp image operates as an unprivileged user, my library can't do these things. Ideally, when root powers were needed I'd have the ability to provide a password to my library so it could temporarily bump up its access rights in order to get things done. Alas, I haven't been able to find any Common Lisp equivalent of sudo. Is there one? Am I approaching this the wrong way? How might I go about solving this?

In code, what I want to be able to do is basically this:

(with-sudo (:username "root" :password (securely-read-line))
  (with-open-file (f "/etc/hosts" :direction :output :if-exists :append)
    (format f "127.0.0.1 mywebsite.local~%")))

Clarifications

I'm using SBCL on OS X. I'm trying to create a library which is basically a specialization of quickproject for websites. Currently, every time I set up a new web project on my local computer I have to edit configuration files strewn across my system. I would like to automate as much of that as possible, and I would like to be able to do this from within the SLIME session that I usually have open and connected to a single instance of SBCL.

Here are some other considerations:

  1. I don't want my main instance of SBCL to be setuid'd to root
  2. I don't mind spawning a new process, whether that's a small C program or a bare instance of SBCL that loads a few lines of code then exits.
  3. I would like to use keep the Lisp to C ratio as high as possible.
  4. Some text editors (such as TextMate) bring up a prompt asking for a username and password when visiting files that require heightened privileges to view, providing access accordingly. I wonder how I could get my Lisp library to do the same?
  5. At first I couldn't get sudo (the actual program itself) to work from within SBCL:

    (inferior-shell:run/ss '("sudo" "ls" ".")) ;; how can I pass sudo a password?

    Now it looks like that will be possible using sudo's -S option (thank you JustAnotherCurious). I think I may just go this route; it's definitely what I'm leaning towards now.

Anyhow, thank you everyone! I'm learning a lot from all of you.

Was it helpful?

Solution

If you need root access privileges in your process, you need to start your Lisp process as root initially. It is not generally possible to make a non-root process run as in retrospect.

Fortunately, Unix has a mechanism that allows a process to switch between root and non-root privileges at run time. That mechanism is called effective user id. A process that runs as root can switch to a non-root effective uid using the seteuid system call, and it can also switch back to "being" root that way.

Certainly, if you start your Lisp process as root, that process has full control over the machine, and depending on what data and machine you're dealing with, you need to be considerate as to what possible security holes you open by that. Fortunately, buffer overflows are hard to produce in Lisp, so from that perspective, you're on the safer side :)

Access to the system call interface is not standardized in Common Lisp, but most implementations have a native interface to the system, and you can also use CFFI if you plan for your program to be portable across Linux/Unix based Lisps.

Here is a transcript of SBCL running as root demonstrating the use of seteuid:

CL-USER> (defun write-file-in-filesystem-root ()
           (handler-case
               (with-open-file (f "/only-root-may-write-to-root"
                                  :direction :output
                                  :if-exists :supersede)
                 (write "hello" :stream f))
             (error (e) (format t "error: ~A~%" e))))
WRITE-FILE-IN-FILESYSTEM-ROOT
CL-USER> (sb-posix:seteuid 0)
0
CL-USER> (write-file-in-filesystem-root)
"hello"
CL-USER> (sb-posix:seteuid 1000)
0
CL-USER> (write-file-in-filesystem-root)
error: error opening #P"/only-root-may-write-to-root": Permission denied
NIL
CL-USER> (sb-posix:seteuid 0)
0
CL-USER> (write-file-in-filesystem-root)
"hello"
CL-USER> (delete-file "/only-root-may-write-to-root")
T

If all you need is access to protected files, if staying OSX specific is acceptable and if you want the user to authenticate using the standard authentication requester, you can use the authopen command which is specific to OSX.

OTHER TIPS

First, you cannot and should not be able to violate the Linux permissions system. If there was some way to abuse the permissions & authentication system, that would be a huge security hole: in effect, everyone would have root access. If you want such insecurity, go back to MSDOS!

Then, you could configure your system to give root access to almost everything. You could configure the sudo or the super commands to not ask any password (I actually knowingly do that on some machines, but I do know it is -or may be abused as- a security hole). This means trusting the entity (human or program) running that  sudo command.

Also, some programs (e.g. /bin/login, /usr/bin/sudo, /usr/bin/super ....) can be setuid (read also credentials(7) and execve(2) man pages), and your Common Lisp implementation perhaps provide a wrapper to the setuid(2) or setreuid(2) syscalls (or you could write that wrapper calling the C syscall by yourself).

However, you just seem to want to add the 127.0.0.1 mywebsite.local entry to /etc/hosts; why can't you make such an entry a prerequisite (so, in the human procedure for installing your Common Lisp software, you require -to the human sysadmin- that such an entry should be added). Then, all you have to do is to have someway to pass the mywebsite.local name to your Common Lisp program, and that should be easy.

I would warn against using setuid binary programs written in Common Lisp. Because, unless you take special precautions, most Common Lisp binaries contain (indirectly thru eval) the entire Common Lisp compiler. And giving root access to a dynamic & powerful compiler like Common Lisp (e.g. SBCL) is a security hole.

If you insist on adding mywebsite.local programmatically to /etc/hosts from your Common Lisp library, you could write a tiny C helper program, whose executable e.g. /usr/local/libexec/helperbinary is root-setuid (i.e. you chown root /usr/local/libexec/helperbinary then chmod u+s /usr/local/libexec/helperbinary at installation time and you obviously need to be root to run these chown and chmod), which does exactly this and can be started only -thru appropriate tricks- from your Common Lisp binary. Be very careful when coding that tiny helper program in C, e.g. read Advanced Linux Programming, and proof-read your C code several times (you can easily make security holes in setuid programs).

The problem is that you do not "bump up" access rights: you start with superuser privileges and drop them when you no longer need them (the sudo program you're referring to is marked setuid root to achieve this).

Therefore, what you want to achieve would require starting your LISP interpreter as root, then have it fork another interpreter process and have that process drop its superuser privileges immediately. From there on, the unprivileged process can run your code as usual and use IPC to the privileged process to have it, say, write to /etc/hosts.

This will not be trivial to implement, unfortunately.

If your actions with root access are limited to some small and easy actions and you are allowed to write some platform specific code, then, i think it would work, you can try to execute bash (or other shell) commands from your code with sudo -S.

from sudo man

The -S (stdin) option causes sudo to read the password from 
the standard input instead of the terminal device. 
The password must be followed by a newline character.

But I think it's better not to do such things at all.

If you just want to do some task as root with a command, you don't need to start your Lisp interpreter as root, just as your bash/zsh isn't started as root but can still run sudo just fine.

how can I pass sudo a password?

If you're using inferior-shell:

(inferior-shell:run/interactive '("sudo" "ls" "."))

Otherwise with plain UIOP:

(uiop:run-program '("sudo" "whoami")
                  :input :interactive
                  :output :interactive)

If you do want to run Lisp code and not just an external command, you would have to do something ugly like this:

(string-trim
 '(#\space #\return #\linefeed)
 (uiop:run-program
  ;; Notice how "sbcl" is hardcoded. Making this portable is also non-trivial.
  '("sudo" "sbcl" "--noinform" "--non-interactive" "--eval" "(print (+ 1 2))")
  :input :interactive
  :output :interactive))

With enough effort (somehow capture the current environment and reestablish it in the new process, maybe processing the output value so that you can eg. list a privileged directory and return it as a list of paths...), it is probably possible to implement a with-sudo macro that transparently asks the user for more permission to run more Lisp code as root. But it is non-trivial.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top