DaemonForums  

Go Back   DaemonForums > OpenBSD > OpenBSD Security

OpenBSD Security Functionally paranoid!

Reply
 
Thread Tools Display Modes
  #1   (View Single Post)  
Old 19th January 2016
jggimi's Avatar
jggimi jggimi is offline
More noise than signal
 
Join Date: May 2008
Location: USA
Posts: 7,977
Default pledge() - a love story

Preface

For those unfamiliar with it, pledge(2) is a system call (a "syscall") that a programmer can use to limit her program to specific subsets of future syscalls. It's a relatively new risk mitigation feature for OpenBSD, initiated by applications.

Most of the userland applications in OpenBSD have had pledge() added for 5.9. Some key ports in the ports tree have also recently added pledge() protections, such as the www/chromium browser, as discussed recently in the OpenBSD Journal.

The Story

A list of ports that should use pledge() was posted to the ports@ mailing list this past weekend. One of those appplications was archivers/p7zip, a portable version of the Windows 7zip program. I maintain the OpenBSD port.

The application is 80+K lines of undocumented code, spread across more than 200 source code modules. And I'm not a programmer.
IT guy? Sure.
Whiner? You bet.
Know-it-all? Of course.
But programmer? Nope.
I've been away from programming for at least 25 years, and only recently taught myself the rudiments of C and C++. I can read source code, but some things I must still refer to documentation to understand, and when crafting my own programs, I often confuse when I need pointers to pointers or just pointers, or whether I should reference or dereference.

While I maintain this port, I have little understanding of this application's inner workings. But the culture of the project is very clearly, "Don't ask for something to be developed for you. Develop it yourself. If you can't do it, at least try. If you fail when trying, you'll know what knowledge you need to acquire or what help you need to request." So I decided I must try.

I began with an empty string in a pledge() promise near the beginning of the main process function, and built the application with debugging enabled and optimization off. And started looking through stack traces, adding categories of syscalls to the promise string in my pledge() as I progressed, then reviewing the next stack trace with each iteration.

And when I saw code paths diverge, I learned I needed to add another pledge point. It was easy. The stack traces in the core files let me see the program flow with each change.

I posted the patch to the ports@ mailing list. First when I hadn't yet determined what to do about the code path divergence, then a revised patch after I'd figured it out.

I received an Email today confirming the patch is in review and test. It appears (to quote the developer) "sane" and it is being more fully tested to see if it can be broken. If not, it will be committed.

Summary

My total time invested in this? Six hours. For a very large, complicated, undocumented application, by a self-described non-programmer.

Key tools used :
  • the pledge(2) man page
  • setting CFLAGS and CXXFLAGS both to +="-g -O0" in the port Makefile
  • gdb(1) to look through stack traces
  • rm(1) to remove the port's .build_done tokens
  • make(1) with ports(7) targets update-patches, build, and test
The tl;dr:

The pledge() tool is easy to use. If I can use it, so can you.

---

Here's are links to Theo de Raadt's slides and video on pledge() from Hackfest 2015.

Last edited by jggimi; 19th January 2016 at 07:33 PM. Reason: typo, clarity
Reply With Quote
  #2   (View Single Post)  
Old 19th January 2016
jggimi's Avatar
jggimi jggimi is offline
More noise than signal
 
Join Date: May 2008
Location: USA
Posts: 7,977
Default

Here's the excerpt from my post to to ports@ containing the new patch file. Since this is a new file intended to be added with patch(1), all lines in the file are inserts and begin with a + character. Only the lines that begin with ++ are actually inserted into the application. Those merely add two pledge() calls at different points in the main module, wrapped in pre-processor token tests -- this application builds the same module multiple ways for different executable binaries.

They are also nearly the only documented code fragments in the application.
Code:
Index: patches/patch-CPP_7zip_UI_Console_Main_cpp
===================================================================
RCS file: patches/patch-CPP_7zip_UI_Console_Main_cpp
diff -N patches/patch-CPP_7zip_UI_Console_Main_cpp
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ patches/patch-CPP_7zip_UI_Console_Main_cpp	18 Jan 2016 15:05:44 -0000
@@ -0,0 +1,43 @@
+$OpenBSD$
+
+Pledge archivers/p7zip binaries
+
+--- CPP/7zip/UI/Console/Main.cpp.orig	Sat Oct 17 11:20:22 2015
++++ CPP/7zip/UI/Console/Main.cpp	Mon Jan 18 10:05:31 2016
+@@ -484,6 +484,18 @@ int Main2(
+   #endif
+ )
+ {
++
++// pledge 7za and 7zr at this point, they take different paths than 7z.  
++
++#ifndef EXTERNAL_CODECS
++
++  if (pledge("stdio rpath wpath cpath fattr", NULL) == -1) {
++    perror("pledge");
++    exit(1);
++  }
++
++#endif
++
+   #if defined(_WIN32) && !defined(UNDER_CE)
+   SetFileApisToOEM();
+   #endif
+@@ -579,6 +591,17 @@ int Main2(
+   codecs->CaseSensitiveChange = options.CaseSensitiveChange;
+   codecs->CaseSensitive = options.CaseSensitive;
+   ThrowException_if_Error(codecs->Load());
++
++// pledge 7z here
++
++#ifdef EXTERNAL_CODECS
++
++  if (pledge("stdio rpath wpath cpath fattr", NULL) == -1) {
++    perror("pledge");
++    exit(1);
++  }
++
++#endif
+ 
+   bool isExtractGroupCommand = options.Command.IsFromExtractGroup();
+
Reply With Quote
  #3   (View Single Post)  
Old 22nd January 2016
Carpetsmoker's Avatar
Carpetsmoker Carpetsmoker is offline
Real Name: Martin
Tcpdump Spy
 
Join Date: Apr 2008
Location: Netherlands
Posts: 2,243
Default

An introduction to pledge(), for those who are not familiar with it.

One thing you can do In Capsicum, but not with pledge() as far as I can see, is stuff such as
(from capsicum(4)):

Code:
	     Limit operations that can be called on file descriptors.  For
	     example, a	file descriptor	returned by open(2) may	be refined
	     using cap_rights_limit(2) so that only read(2) and	write(2) can
	     be	called,	but not	fchmod(2).  The	complete list of the capabil-
	     ity rights	can be found in	the rights(4) manual page.
So it seems a bit more fine-grained. In your pk7zip example, for extract operations you could say that it's only allowed to read (but never write) from a single file descriptor and only write to file descriptors under /extract/point. This would, of course, require a lot more coding than what you did...

Not sure how well this works in the real world... In the linked slides it's claimed that Capsicum is too complicated. But on the other hand, pledge() seems a bit too simple in its current incarnation...
__________________
UNIX was not designed to stop you from doing stupid things, because that would also stop you from doing clever things.
Reply With Quote
  #4   (View Single Post)  
Old 22nd January 2016
jggimi's Avatar
jggimi jggimi is offline
More noise than signal
 
Join Date: May 2008
Location: USA
Posts: 7,977
Default

Subsequent pledge(2) calls will further restrict capabilities. But from looking at the code paths taken it appeared -- to me -- that the functional structure of this app precludes someone with my skills from approaching it for methodical pledging. The chromium example I mentioned above was a vastly more difficult and complicated undertaking, but in looking at those patches, I perceive that its software architecture lends it to structured governance mechanisms such as pledge().

But even a wide-open pledge() string significantly reduces the range of kernel services available to the calling process. And my pledge string is certainly narrower than some.

---

I've been through dozens, perhaps as many as 100 stack traces, and pushed the code through its paces, but I cannot say I understand its operation. The p7zip app is a portability port of the Windows app 7zip, and neither upstream documents their code base. In most of the 219 source code modules, the only comment is the file's name on line 1.

There are further improvements I could make to this patch even with my limited skills and limited understanding of the application. For example:
  • I could add a debug preprocessor token and printf() calls for use with the test suite, to ensure that the pledge calls are not bypassed by any of the tests. I added those printf() calls manually during my tests, and removed them when I prepared the patch for publication.
  • I could wrap the calls in __OpenBSD__ tokens and recommend the patch to upstream, though from memory none of my prior recommendations or patches were accepted, and I have no expectation that this sort of patch would be accepted either.
If you'd like to help me improve the granularity of the pledge() governance for this app, I would welcome it.

Last edited by jggimi; 22nd January 2016 at 04:23 PM. Reason: clarity and typos
Reply With Quote
  #5   (View Single Post)  
Old 23rd January 2016
e1-531g e1-531g is offline
ISO Quartermaster
 
Join Date: Mar 2014
Posts: 628
Default

I am not expert on pledge and chroot in OpenBSD, so don't get me too serious on this.
If I understand correctly enough, for me one thing missing is ability to restrict processes also after fork() AND execve().
I think it could be used to build sandbox environment using chroot().
Create chroot, then drop privileges inside chroot (became another user), then pledge not using system calls referring to login as another user (especially root) and after that start Firefox.
I think it could be quite difficult for attacker to go out of this chroot, read files outside chroot and send them through Internet.
But maybe I am wrong on something.

Add2:
I understand that for some programs it is not fitting well for example shell i.e. ksh should be able to pledge itself tightly and also processes executed by ksh should not be pledged by default at all.
On the other hand maybe there is a place for second kind of promise exec to also pledge other executed programs (fork() AND execve()).
But maybe there are more difficulties than I see.

Last edited by e1-531g; 23rd January 2016 at 01:37 PM. Reason: Added second part of post
Reply With Quote
  #6   (View Single Post)  
Old 23rd January 2016
jggimi's Avatar
jggimi jggimi is offline
More noise than signal
 
Join Date: May 2008
Location: USA
Posts: 7,977
Default

It is not a chroot(). Its restrictions only impact the calling process.

Even though the currently unimplemented whitelist parameter will further restrict the capability of *path promises, if a new application is started by the pledged application, the new process is started without pledge() restrictions. See the exec promise in the pledge(2) man page.
Reply With Quote
  #7   (View Single Post)  
Old 23rd January 2016
e1-531g e1-531g is offline
ISO Quartermaster
 
Join Date: Mar 2014
Posts: 628
Default

@jggimi
I understand that. I must admit that I do not discussed yours patch but rather my view of pledge() in general.
I have even wrote small program in C (not good style, so I would rather not post source code) to see if I just fork() without execve() pledge would kill my process and yes, it kills process.
I have also added few words to previous post in parallel to you writing yours post.
Reply With Quote
  #8   (View Single Post)  
Old 23rd January 2016
jggimi's Avatar
jggimi jggimi is offline
More noise than signal
 
Join Date: May 2008
Location: USA
Posts: 7,977
Default

Since you mention ksh, Ted Unangst posted about pledge in his blog, and mentions ksh applicability, though the post is really about uncovering bugs in applications when pledge is applied.
Reply With Quote
  #9   (View Single Post)  
Old 25th January 2016
jggimi's Avatar
jggimi jggimi is offline
More noise than signal
 
Join Date: May 2008
Location: USA
Posts: 7,977
Default

I had an offer of assistance from a developer today, so I took it. He'd noted a CVE that had not been addressed by upstream, and he spent some time devising pledge() call points to revoke privileges for functions that do not write to disk.

I adopted Debian's mitigation for the CVE, added the debugging tokens I'd mentioned planning above, and the two of us spent a good deal of time testing the secondary pledge() calls today. The included test suite is only a partial test of all capabilities, and so we were doing lots of manual testing also. One thing uncovered was a missing pledge() of self-extracting archives. Not very useful on OpenBSD, but certainly possible.

Specifics and patches were just posted to ports@.

Last edited by jggimi; 25th January 2016 at 03:48 AM. Reason: I can't post without typos
Reply With Quote
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump

Similar Threads
Thread Thread Starter Forum Replies Last Post
Share your BSD story for the BSD Now Holiday show ibara News 1 14th December 2014 06:22 PM
Migrating from iptables to pf, a love story Popelicious OpenBSD Security 7 19th April 2013 08:46 AM
OpenBSD A Puffy in the corporate aquarium [success story] vermaden News 2 22nd April 2011 01:08 AM
Learning Programming Crypt Programming 35 27th October 2008 04:54 PM
Learning Perl mtx Book reviews 7 22nd October 2008 05:55 PM


All times are GMT. The time now is 12:00 PM.


Powered by vBulletin® Version 3.8.4
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Content copyright © 2007-2010, the authors
Daemon image copyright ©1988, Marshall Kirk McKusick