Baseline firejail 0.9.28

This commit is contained in:
netblue30 2015-08-08 19:12:30 -04:00
parent f104ebb698
commit 1379851360
246 changed files with 33999 additions and 0 deletions

280
COPYING Normal file
View file

@ -0,0 +1,280 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS

156
Makefile.in Normal file
View file

@ -0,0 +1,156 @@
all: apps firejail.1 firemon.1 firejail-profile.5 firejail-login.5
MYLIBS = src/lib
APPS = src/firejail src/firemon src/libtrace src/ftee
datarootdir=@datarootdir@
PREFIX=@prefix@
prefix=@prefix@
VERSION=@PACKAGE_VERSION@
NAME=@PACKAGE_NAME@
PACKAGE_TARNAME=@PACKAGE_TARNAME@
DOCDIR=@docdir@
.PHONY: mylibs $(MYLIBS)
mylibs: $(MYLIBS)
$(MYLIBS):
$(MAKE) -C $@
.PHONY: apps $(APPS)
apps: $(APPS)
$(APPS): $(MYLIBS)
$(MAKE) -C $@
firemon.1: src/man/firemon.txt
./mkman.sh $(VERSION) src/man/firemon.txt firemon.1
firejail.1: src/man/firejail.txt
./mkman.sh $(VERSION) src/man/firejail.txt firejail.1
firejail-profile.5: src/man/firejail-profile.txt
./mkman.sh $(VERSION) src/man/firejail-profile.txt firejail-profile.5
firejail-login.5: src/man/firejail-login.txt
./mkman.sh $(VERSION) src/man/firejail-login.txt firejail-login.5
clean:;
for dir in $(APPS); do \
$(MAKE) -C $$dir clean; \
done
for dir in $(MYLIBS); do \
$(MAKE) -C $$dir clean; \
done
rm -f firejail.1 firejail.1.gz firemon.1 firemon.1.gz firejail-profile.5 firejail-profile.5.gz firejail-login.5 firejail-login.5.gz
distclean: clean
for dir in $(APPS); do \
$(MAKE) -C $$dir distclean; \
done
for dir in $(MYLIBS); do \
$(MAKE) -C $$dir distclean; \
done
rm -fr Makefile autom4te.cache config.log config.status config.h
install: all
# firejail executable
strip src/firejail/firejail
mkdir -p $(DESTDIR)/$(PREFIX)/bin
install -c -m 0755 src/firejail/firejail $(DESTDIR)/$(PREFIX)/bin/.
chmod u+s $(DESTDIR)/$(PREFIX)/bin/firejail
# firemon executable
strip src/firemon/firemon
install -c -m 0755 src/firemon/firemon $(DESTDIR)/$(PREFIX)/bin/.
# libraries and plugins
strip src/libtrace/libtrace.so
mkdir -p $(DESTDIR)/$(PREFIX)/lib/firejail
install -c -m 0644 src/libtrace/libtrace.so $(DESTDIR)/$(PREFIX)/lib/firejail/.
strip src/ftee/ftee
install -c -m 0755 src/ftee/ftee $(DESTDIR)/$(PREFIX)/lib/firejail/.
install -c -m 0755 src/fshaper/fshaper.sh $(DESTDIR)/$(PREFIX)/lib/firejail/.
# documents
mkdir -p $(DESTDIR)/$(DOCDIR)
install -c -m 0644 COPYING $(DESTDIR)/$(DOCDIR)/.
install -c -m 0644 README $(DESTDIR)/$(DOCDIR)/.
install -c -m 0644 RELNOTES $(DESTDIR)/$(DOCDIR)/.
# etc files
mkdir -p $(DESTDIR)/etc/firejail
install -c -m 0644 etc/audacious.profile $(DESTDIR)/etc/firejail/.
install -c -m 0644 etc/clementine.profile $(DESTDIR)/etc/firejail/.
install -c -m 0644 etc/gnome-mplayer.profile $(DESTDIR)/etc/firejail/.
install -c -m 0644 etc/rhythmbox.profile $(DESTDIR)/etc/firejail/.
install -c -m 0644 etc/totem.profile $(DESTDIR)/etc/firejail/.
install -c -m 0644 etc/firefox.profile $(DESTDIR)/etc/firejail/.
install -c -m 0644 etc/icedove.profile $(DESTDIR)/etc/firejail/.
install -c -m 0644 etc/iceweasel.profile $(DESTDIR)/etc/firejail/.
install -c -m 0644 etc/midori.profile $(DESTDIR)/etc/firejail/.
install -c -m 0644 etc/evince.profile $(DESTDIR)/etc/firejail/.
install -c -m 0644 etc/chromium-browser.profile $(DESTDIR)/etc/firejail/.
install -c -m 0644 etc/chromium.profile $(DESTDIR)/etc/firejail/.
install -c -m 0644 etc/disable-mgmt.inc $(DESTDIR)/etc/firejail/.
install -c -m 0644 etc/disable-secret.inc $(DESTDIR)/etc/firejail/.
install -c -m 0644 etc/disable-common.inc $(DESTDIR)/etc/firejail/.
install -c -m 0644 etc/dropbox.profile $(DESTDIR)/etc/firejail/.
install -c -m 0644 etc/opera.profile $(DESTDIR)/etc/firejail/.
install -c -m 0644 etc/thunderbird.profile $(DESTDIR)/etc/firejail/.
install -c -m 0644 etc/transmission-gtk.profile $(DESTDIR)/etc/firejail/.
install -c -m 0644 etc/transmission-qt.profile $(DESTDIR)/etc/firejail/.
install -c -m 0644 etc/vlc.profile $(DESTDIR)/etc/firejail/.
install -c -m 0644 etc/deluge.profile $(DESTDIR)/etc/firejail/.
install -c -m 0644 etc/qbittorrent.profile $(DESTDIR)/etc/firejail/.
install -c -m 0644 etc/generic.profile $(DESTDIR)/etc/firejail/.
install -c -m 0644 etc/pidgin.profile $(DESTDIR)/etc/firejail/.
install -c -m 0644 etc/xchat.profile $(DESTDIR)/etc/firejail/.
install -c -m 0644 etc/empathy.profile $(DESTDIR)/etc/firejail/.
install -c -m 0644 etc/server.profile $(DESTDIR)/etc/firejail/.
install -c -m 0644 etc/icecat.profile $(DESTDIR)/etc/firejail/.
install -c -m 0644 etc/quassel.profile $(DESTDIR)/etc/firejail/.
install -c -m 0644 etc/deadbeef.profile $(DESTDIR)/etc/firejail/.
install -c -m 0644 etc/filezilla.profile $(DESTDIR)/etc/firejail/.
bash -c "if [ ! -f /etc/firejail/login.users ]; then install -c -m 0644 etc/login.users $(DESTDIR)/etc/firejail/.; fi;"
# man pages
rm -f firejail.1.gz
gzip -9n firejail.1
rm -f firemon.1.gz
gzip -9n firemon.1
rm -f firejail-profile.5.gz
gzip -9n firejail-profile.5
rm -f firejail-login.5.gz
gzip -9n firejail-login.5
mkdir -p $(DESTDIR)/$(PREFIX)/share/man/man1
install -c -m 0644 firejail.1.gz $(DESTDIR)/$(PREFIX)/share/man/man1/.
install -c -m 0644 firemon.1.gz $(DESTDIR)/$(PREFIX)/share/man/man1/.
mkdir -p $(DESTDIR)/$(PREFIX)/share/man/man5
install -c -m 0644 firejail-profile.5.gz $(DESTDIR)/$(PREFIX)/share/man/man5/.
install -c -m 0644 firejail-login.5.gz $(DESTDIR)/$(PREFIX)/share/man/man5/.
rm -f firejail.1.gz firemon.1.gz firejail-profile.5.gz firejail-login.5.gz
# bash completion
mkdir -p $(DESTDIR)/$(PREFIX)/share/bash-completion/completions
install -c -m 0644 etc/firejail.bash_completion $(DESTDIR)/$(PREFIX)/share/bash-completion/completions/firejail
install -c -m 0644 etc/firemon.bash_completion $(DESTDIR)/$(PREFIX)/share/bash-completion/completions/firemon
uninstall:;
rm -f $(DESTDIR)/$(PREFIX)/bin/firejail
rm -f $(DESTDIR)/$(PREFIX)/bin/firemon
rm -fr $(DESTDIR)/$(PREFIX)/lib/firejail
rm -fr $(DESTDIR)/$(PREFIX)/share/doc/firejail
rm -f $(DESTDIR)/$(PREFIX)/share/man/man1/firejail.1*
rm -f $(DESTDIR)/$(PREFIX)/share/man/man1/firemon.1*
rm -f $(DESTDIR)/$(PREFIX)/share/man/man5/firejail-profile.5*
rm -f $(DESTDIR)/$(PREFIX)/share/man/man5/firejail-login.5*
rm -f $(DESTDIR)/$(PREFIX)/share/bash-completion/completions/firejail
rm -f $(DESTDIR)/$(PREFIX)/share/bash-completion/completions/firemon
dist:
make distclean
rm -fr $(NAME)-$(VERSION) $(NAME)-$(VERSION).tar.bz2
mkdir $(NAME)-$(VERSION)
cd $(NAME)-$(VERSION); cp -a ../src .; cp -a ../etc .; cp -a ../platform .; cp -a ../test .; rm -f src/tools/rvtest; rm -fr src/art; cd ..
cd $(NAME)-$(VERSION); cp -a ../configure .; cp -a ../configure.ac .; cp -a ../Makefile.in .; cp -a ../install.sh .; cp -a ../mkman.sh .; cp -a ../mkdeb.sh .;cd ..
cd $(NAME)-$(VERSION); cp -a ../COPYING .; cp -a ../README .; cp -a ../RELNOTES .; cd ..
cd $(NAME)-$(VERSION); rm -fr `find . -name .svn`; rm -fr $(NAME)-$(VERSION); cd ..
tar -cjvf $(NAME)-$(VERSION).tar.bz2 $(NAME)-$(VERSION)
rm -fr $(NAME)-$(VERSION)
deb: dist
./mkdeb.sh $(NAME) $(VERSION)
extras: all
$(MAKE) -C extras/firetools

28
README Normal file
View file

@ -0,0 +1,28 @@
Firejail is a SUID sandbox program that reduces the risk of security
breaches by restricting the running environment of untrusted applications
using Linux namespaces and seccomp-bpf. It includes sandbox profiles for
Iceweasel/Mozilla Firefox, Chromium, Midori, Opera, Evince, Transmission,
VLC, Audoacious, Clementine, Rhythmbox, Totem, Deluge and qBittorrent.
Firejail also expands the restricted shell facility found in bash by adding
Linux namespace support. It supports sandboxing specific users upon login.
Download: http://sourceforge.net/projects/firejail/files/
Build and install: ./configure && make && sudo make install
Documentation and support: http://firejail.sourceforge.net
License: GPL v2
Firejail Authors:
netblue30 (netblue30@yahoo.com)
Patrick Toomey (http://sourceforge.net/u/ptoomey/profile/)
- user namespace implementation, ticket 10
Reiner Herrmann - a number of build patches, man page fixes (tickets 11, 12, 13, 19)
sshirokov (http://sourceforge.net/u/yshirokov/profile/)
- Patch to output "Reading profile" to stderr instead of stdout (ticket 15)
Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
- src/lib/libnetlink.c extracted from iproute2 software package
G4JC (http://sourceforge.net/u/gaming4jc/profile/)
- ARM support (ticket 17)
Copyright (C) 2014, 2015 Firejail Authors

215
RELNOTES Normal file
View file

@ -0,0 +1,215 @@
firejail (0.9.28) baseline; urgency=low
* network scanning, --scan option
* interface MAC address support, --mac option
* IP address range, --iprange option
* traffic shaping, --bandwidth option
* reworked printing of network status at startup
* man pages rework
* added firejail-login man page
* added GNU Icecat, FileZilla, Pidgin, XChat, Empathy, DeaDBeeF default
profiles
* added an /etc/firejail/disable-common.inc file to hold common directory
blacklists
* blacklist Opera and Chrome/Chromium config directories in profile files
* support noroot option for profile files
* enabled noroot in default profile files
* bugfixes
-- netblue30 <netblue30@yahoo.com> Sat, 1 Aug 2015 08:00:00 -0500
firejail (0.9.26) baseline; urgency=low
* private dev directory
* private.keep option for whitelisting home files in a new private directory
* user namespaces support, noroot option
* added Deluge and qBittorent profiles
* bugfixes
-- netblue30 <netblue30@yahoo.com> Thu, 30 Apr 2015 08:00:00 -0500
firejail (0.9.24) baseline; urgency=low
* whitelist and blacklist seccomp filters
* doubledash option
* --shell=none support
* netfilter file support in profile files
* dns server support in profile files
* added --dns.print option
* added default profiles for Audacious, Clementine, Gnome-MPlayer, Rhythmbox and Totem.
* added --caps.drop=all in default profiles
* new syscalls in default seccomp filter: sysfs, sysctl, adjtimex, kcmp
* clock_adjtime, lookup_dcookie, perf_event_open, fanotify_init
* Bugfix: using /proc/sys/kernel/pid_max for the max number of pids
* two build patches from Reiner Herman (tickets 11, 12)
* man page patch from Reiner Herman (ticket 13)
* output patch (ticket 15) from sshirokov
-- netblue30 <netblue30@yahoo.com> Sun, 5 Apr 2015 08:00:00 -0500
firejail (0.9.22) baseline; urgency=low
* Replaced --noip option with --ip=none
* Container stdout logging and log rotation
* Added process_vm_readv, process_vm_writev and mknod to
* default seccomp blacklist
* Added CAP_MKNOD to default caps blacklist
* Blacklist and whitelist custom Linux capabilities filters
* macvlan device driver support for --net option
* DNS server support, --dns option
* Netfilter support
* Monitor network statistics, --netstats option
* Added profile for Mozilla Thunderbird/Icedove
* - --overlay support for Linux kernels 3.18+
* Bugfix: preserve .Xauthority file in private mode (test with ssh -X)
* Bugfix: check uid/gid for cgroup
-- netblue30 <netblue30@yahoo.com> Mon, 9 Mar 2015 09:00:00 -0500
firejail (0.9.20) baseline; urgency=low
* utmp, btmp and wtmp enhancements
* create empty /var/log/wtmp and /var/log/btmp files in sandbox
* generate a new /var/run/utmp file in sandbox
* CPU affinity, --cpu option
* Linux control groups support, --cgroup option
* Opera web browser support
* VLC support
* Added "empty" attribute to seccomp command to remove the default
* syscall list form seccomp blacklist
* Added --nogroups option to disable supplementary groups for regular
* users. root user always runs without supplementary groups.
* firemon enhancements
* display the command that started the sandbox
* added --caps option to display capabilities for all sandboxes
* added --cgroup option to display the control groups for all sandboxes
* added --cpu option to display CPU affinity for all sandboxes
* added --seccomp option to display seccomp setting for all sandboxes
* New compile time options: --disable-chroot, --disable-bind
* bugfixes
-- netblue30 <netblue30@yahoo.com> Mon, 02 Feb 2015 08:00:00 -0500
firejail (0.9.18) baseline; urgency=low
* Support for tracing system, setuid, setgid, setfsuid, setfsgid syscalls
* Support for tracing setreuid, setregid, setresuid, setresguid syscalls
* Added profiles for transmission-gtk and transmission-qt
* bugfixes
-- netblue30 <netblue30@yahoo.com> Fri, 25 Dec 2014 10:00:00 -0500
firejail (0.9.16) baseline; urgency=low
* Configurable private home directory
* Configurable default user shell
* Software configuration support for --docdir and DESTDIR
* Profile file support for include, caps, seccomp and private keywords
* Dropbox profile file
* Linux capabilities and seccomp filters enabled by default for Firefox,
Midori, Evince and Dropbox
* bugfixes
-- netblue30 <netblue30@yahoo.com> Tue, 4 Nov 2014 10:00:00 -0500
firejail (0.9.14) baseline; urgency=low
* Linux capabilities and seccomp filters are automatically enabled in
chroot mode (--chroot option) if the sandbox is started as regular user
* Added support for user defined seccomp blacklists
* Added syscall trace support
* Added --tmpfs option
* Added --balcklist option
* Added --read-only option
* Added --bind option
* Logging enhancements
* --overlay option was reactivated
* Added firemon support to print the ARP table for each sandbox
* Added firemon support to print the route table for each sandbox
* Added firemon support to print interface information for each sandbox
* bugfixes
-- netblue30 <netblue30@yahoo.com> Tue, 15 Oct 2014 10:00:00 -0500
firejail (0.9.12.2) baseline; urgency=low
* Fix for pulseaudio problems
* --overlay option was temporarily disabled in this build
-- netblue30 <netblue30@yahoo.com> Mon, 29 Sept 2014 07:00:00 -0500
firejail (0.9.12.1) baseline; urgency=low
* Fix for pulseaudio problems
* --overlay option was temporarily disabled in this build
-- netblue30 <netblue30@yahoo.com> Mon, 22 Sept 2014 09:00:00 -0500
firejail (0.9.12) baseline; urgency=low
* Added capabilities support
* Added support for CentOS 7
* bugfixes
-- netblue30 <netblue30@yahoo.com> Mon, 15 Sept 2014 10:00:00 -0500
firejail (0.9.10) baseline; urgency=low
* Disable /proc/kcore, /proc/kallsyms, /dev/port, /boot
* Fixed --top option CPU utilization calculation
* Implemented --tree option in firejail and firemon
* Implemented --join=name option
* Implemented --shutdown option
* Preserve the current working directory if possible
* Cppcheck and clang errors cleanup
* Added a Chromium web browser profile
-- netblue30 <netblue30@yahoo.com> Thu, 28 Aug 2014 07:00:00 -0500
firejail (0.9.8.1) baseline; urgency=low
* FIxed a number of bugs introduced in 0.9.8
-- netblue30 <netblue30@yahoo.com> Fri, 25 Jul 2014 07:25:00 -0500
firejail (0.9.8) baseline; urgency=low
* Implemented nowrap mode for firejail --list command option
* Added --top option in both firejail and firemon
* seccomp filter support
* Added pid support for firemon
* bugfixes
-- netblue30 <netblue30@yahoo.com> Tue, 24 Jul 2014 08:51:00 -0500
firejail (0.9.6) baseline; urgency=low
* Mounting tmpfs on top of /var/log, required by several server programs
* Server fixes for /var/lib and /var/cache
* Private mode fixes
* csh and zsh default shell support
* Chroot mode fixes
* Added support for lighttpd, isc-dhcp-server, apache2, nginx, snmpd,
-- netblue30 <netblue30@yahoo.com> Sat, 7 Jun 2014 09:00:00 -0500
firejail (0.9.4) baseline; urgency=low
* Fixed resolv.conf on Ubuntu systems using DHCP
* Fixed resolv.conf on Debian systems using resolvconf package
* Fixed /var/lock directory
* Fixed /var/tmp directory
* Fixed symbolic links in profile files
* Added profiles for evince, midori
-- netblue30 <netblue30@yahoo.com> Sun, 4 May 2014 08:00:00 -0500
firejail (0.9.2) baseline; urgency=low
* Checking IP address passed with --ip option using ARP; exit if the address
is already present
* Using a lock file during ARP address assignment in order to removed a race
condition.
* Several fixes to --private option; it also mounts a tmpfs filesystem on top
of /tmp
* Added user access check for profile file
* Added --defaultgw option
* Added support of --noip option; it is necessary for DHCP setups
* Added syslog support
* Added support for "tmpfs" and "read-only" profile commands
* Added an expect-based testing framework for the project
* Added bash completion support
* Added support for multiple networks
-- netblue30 <netblue30@yahoo.com> Fri, 25 Apr 2014 08:00:00 -0500
firejail (0.9) baseline; urgency=low
* First beta version
-- netblue30 <netblue30@yahoo.com> Sat, 12 Apr 2014 09:00:00 -0500

4723
configure vendored Executable file

File diff suppressed because it is too large Load diff

52
configure.ac Normal file
View file

@ -0,0 +1,52 @@
AC_PREREQ([2.68])
AC_INIT(firejail, 0.9.28, netblue30@yahoo.com, , http://firejail.sourceforge.net)
AC_CONFIG_SRCDIR([src/firejail/main.c])
#AC_CONFIG_HEADERS([config.h])
AC_PROG_CC
#AC_PROG_CXX
AC_PROG_INSTALL
AC_PROG_RANLIB
HAVE_SECCOMP=""
AC_ARG_ENABLE([seccomp],
AS_HELP_STRING([--disable-seccomp], [Disable seccomp]))
AS_IF([test "x$enable_seccomp" != "xno"], [
HAVE_SECCOMP="-DHAVE_SECCOMP"
AC_SUBST(HAVE_SECCOMP)
])
HAVE_CHROOT=""
AC_ARG_ENABLE([chroot],
AS_HELP_STRING([--disable-chroot], [Disable chroot]))
AS_IF([test "x$enable_chroot" != "xno"], [
HAVE_CHROOT="-DHAVE_CHROOT"
AC_SUBST(HAVE_CHROOT)
])
HAVE_BIND=""
AC_ARG_ENABLE([bind],
AS_HELP_STRING([--disable-bind], [Disable bind]))
AS_IF([test "x$enable_bind" != "xno"], [
HAVE_BIND="-DHAVE_BIND"
AC_SUBST(HAVE_BIND)
])
# checking pthread library
AC_CHECK_LIB([pthread], [main], [], AC_MSG_ERROR([*** POSIX thread support not installed ***]))
AC_CHECK_HEADER(pthread.h,,AC_MSG_ERROR([*** POSIX thread support not installed ***]))
AC_CHECK_HEADER([linux/seccomp.h], HAVE_SECCOMP_H="-DHAVE_SECCOMP_H", HAVE_SECCOMP_H="")
AC_SUBST(HAVE_SECCOMP_H)
AC_OUTPUT(Makefile src/lib/Makefile src/firejail/Makefile src/firemon/Makefile src/libtrace/Makefile src/ftee/Makefile)
echo
echo "Configuration options:"
echo " prefix: $prefix"
echo " seccomp: $HAVE_SECCOMP"
echo " <linux/seccomp.h>: $HAVE_SECCOMP_H"
echo " chroot: $HAVE_CHROOT"
echo " bind: $HAVE_BIND"
echo

8
etc/audacious.profile Normal file
View file

@ -0,0 +1,8 @@
# Audacious profile
include /etc/firejail/disable-mgmt.inc
include /etc/firejail/disable-secret.inc
include /etc/firejail/disable-common.inc
caps.drop all
seccomp
noroot

View file

@ -0,0 +1,3 @@
# Chromium browser profile
include /etc/firejail/chromium.profile

7
etc/chromium.profile Normal file
View file

@ -0,0 +1,7 @@
# Chromium browser profile
include /etc/firejail/disable-mgmt.inc
include /etc/firejail/disable-secret.inc
include /etc/firejail/disable-common.inc chromium
netfilter

7
etc/clementine.profile Normal file
View file

@ -0,0 +1,7 @@
# Clementine profile
include /etc/firejail/disable-mgmt.inc
include /etc/firejail/disable-secret.inc
include /etc/firejail/disable-common.inc
caps.drop all
seccomp
noroot

8
etc/deadbeef.profile Normal file
View file

@ -0,0 +1,8 @@
# DeaDBeeF profile
include /etc/firejail/disable-mgmt.inc
include /etc/firejail/disable-secret.inc
include /etc/firejail/disable-common.inc
caps.drop all
seccomp
noroot

9
etc/deluge.profile Normal file
View file

@ -0,0 +1,9 @@
# deluge profile
include /etc/firejail/disable-mgmt.inc
include /etc/firejail/disable-secret.inc
include /etc/firejail/disable-common.inc
caps.drop all
seccomp
netfilter
noroot

10
etc/disable-common.inc Normal file
View file

@ -0,0 +1,10 @@
blacklist ${HOME}/.adobe
blacklist ${HOME}/.macromedia
blacklist ${HOME}/.mozilla
blacklist ${HOME}/.icedove
blacklist ${HOME}/.thunderbird
blacklist ${HOME}/.config/midori
blacklist ${HOME}/.config/opera
blacklist ${HOME}/.config/chromium
blacklist ${HOME}/.config/google-chrome
blacklist ${HOME}/.filezilla

12
etc/disable-mgmt.inc Normal file
View file

@ -0,0 +1,12 @@
# system directories
blacklist /sbin
blacklist /usr/sbin
# system management
blacklist ${PATH}/umount
blacklist ${PATH}/mount
blacklist ${PATH}/fusermount
blacklist ${PATH}/su
blacklist ${PATH}/sudo
blacklist ${PATH}/xinput
blacklist ${PATH}/strace

9
etc/disable-secret.inc Normal file
View file

@ -0,0 +1,9 @@
# HOME directory
blacklist ${HOME}/.ssh
tmpfs ${HOME}/.gnome2_private
blacklist ${HOME}/.gnome2/keyrings
blacklist ${HOME}/kde4/share/apps/kwallet
blacklist ${HOME}/kde/share/apps/kwallet
blacklist ${HOME}/.pki/nssdb
blacklist ${HOME}/.gnupg
blacklist ${HOME}/.local/share/recently-used.xbel

7
etc/dropbox.profile Normal file
View file

@ -0,0 +1,7 @@
# dropbox profile
include /etc/firejail/disable-mgmt.inc
include /etc/firejail/disable-secret.inc
include /etc/firejail/disable-common.inc
caps
seccomp
noroot

6
etc/empathy.profile Normal file
View file

@ -0,0 +1,6 @@
# Empathy profile
include /etc/firejail/disable-mgmt.inc
include /etc/firejail/disable-secret.inc
include /etc/firejail/disable-common.inc
caps.drop all
seccomp

8
etc/evince.profile Normal file
View file

@ -0,0 +1,8 @@
# evince profile
include /etc/firejail/disable-mgmt.inc
include /etc/firejail/disable-secret.inc
include /etc/firejail/disable-common.inc
caps.drop all
seccomp
netfilter
noroot

10
etc/filezilla.profile Normal file
View file

@ -0,0 +1,10 @@
# FileZilla profile
include /etc/firejail/disable-mgmt.inc
include /etc/firejail/disable-secret.inc
include /etc/firejail/disable-common.inc .filezilla
caps.drop all
seccomp
noroot
netfilter

9
etc/firefox.profile Normal file
View file

@ -0,0 +1,9 @@
# Firejail profile for Mozilla Firefox (Iceweasel in Debian)
include /etc/firejail/disable-mgmt.inc
include /etc/firejail/disable-secret.inc
include /etc/firejail/disable-common.inc .mozilla
caps.drop all
seccomp
netfilter
noroot

View file

@ -0,0 +1,86 @@
# bash completion for firejail -*- shell-script -*-
#********************************************************************
# Script based on completions/configure script in bash-completion package in
# Debian. The original package is release under GPL v2 license, the webpage is
# http://bash-completion.alioth.debian.org
#*******************************************************************
__interfaces(){
cut -f 1 -d ':' /proc/net/dev | tail -n +3 | grep -v lo | xargs
}
_firejail()
{
local cur prev words cword split
_init_completion -s || return
case $prev in
--help|--version|-debug-caps|--debug-syscalls|--list|--tree|--top|--join|--shutdown)
return 0
;;
--profile)
_filedir
return 0
;;
--chroot)
_filedir -d
return 0
;;
--cgroup)
_filedir -d
return 0
;;
--tmpfs)
_filedir
return 0
;;
--blacklist)
_filedir
return 0
;;
--read-only)
_filedir
return 0
;;
--bind)
_filedir
return 0
;;
--private)
_filedir
return 0
;;
--shell)
_filedir
return 0
;;
--net)
comps=$(__interfaces)
COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
return 0
;;
esac
$split && return 0
# if $COMP_CONFIGURE_HINTS is not null, then completions of the form
# --option=SETTING will include 'SETTING' as a contextual hint
[[ "$cur" != -* ]] && _filedir && return 0
if [[ -n $COMP_CONFIGURE_HINTS ]]; then
COMPREPLY=( $( compgen -W "$( $1 --help 2>&1 | \
awk '/^ --[A-Za-z]/ { print $1; \
if ($2 ~ /--[A-Za-z]/) print $2 }' | sed -e 's/[[,].*//g' )" \
-- "$cur" ) )
[[ $COMPREPLY == *=* ]] && compopt -o nospace
else
COMPREPLY=( $( compgen -W '$( _parse_help "$1" )' -- "$cur" ) )
[[ $COMPREPLY == *= ]] && compopt -o nospace
fi
} &&
complete -F _firejail firejail

View file

@ -0,0 +1,39 @@
# bash completion for firemon -*- shell-script -*-
#********************************************************************
# Script based on completions/configure script in bash-completion package in
# Debian. The original package is release under GPL v2 license, the webpage is
# http://bash-completion.alioth.debian.org
#*******************************************************************
_firemon()
{
local cur prev words cword split
_init_completion -s || return
case $prev in
--help|--version)
return
;;
esac
$split && return 0
# if $COMP_CONFIGURE_HINTS is not null, then completions of the form
# --option=SETTING will include 'SETTING' as a contextual hint
[[ "$cur" != -* ]] && return 0
if [[ -n $COMP_CONFIGURE_HINTS ]]; then
COMPREPLY=( $( compgen -W "$( $1 --help 2>&1 | \
awk '/^ --[A-Za-z]/ { print $1; \
if ($2 ~ /--[A-Za-z]/) print $2 }' | sed -e 's/[[,].*//g' )" \
-- "$cur" ) )
[[ $COMPREPLY == *=* ]] && compopt -o nospace
else
COMPREPLY=( $( compgen -W '$( _parse_help "$1" )' -- "$cur" ) )
[[ $COMPREPLY == *= ]] && compopt -o nospace
fi
} &&
complete -F _firemon firemon

41
etc/generic.profile Normal file
View file

@ -0,0 +1,41 @@
################################
# Generic profile based on Firefox profile
################################
#include /etc/firejail/disable-mgmt.inc
# system directories
blacklist /sbin
blacklist /usr/sbin
# system management
blacklist ${PATH}/umount
blacklist ${PATH}/mount
blacklist ${PATH}/fusermount
blacklist ${PATH}/su
blacklist ${PATH}/sudo
blacklist ${PATH}/xinput
blacklist ${PATH}/strace
#include /etc/firejail/disable-secret.inc
# HOME directory
blacklist ${HOME}/.ssh
tmpfs ${HOME}/.gnome2_private
blacklist ${HOME}/.gnome2/keyrings
blacklist ${HOME}/kde4/share/apps/kwallet
blacklist ${HOME}/kde/share/apps/kwallet
blacklist ${HOME}/.pki/nssdb
blacklist ${HOME}/.gnupg
blacklist ${HOME}/.local/share/recently-used.xbel
blacklist ${HOME}/.adobe
blacklist ${HOME}/.macromedia
blacklist ${HOME}/.mozilla
blacklist ${HOME}/.icedove
blacklist ${HOME}/.thunderbird
blacklist ${HOME}/.config/opera
blacklist ${HOME}/.config/chromium
blacklist ${HOME}/.config/google-chrome
caps.drop all
seccomp
netfilter
noroot

View file

@ -0,0 +1,7 @@
# GNOME MPlayer profile
include /etc/firejail/disable-mgmt.inc
include /etc/firejail/disable-secret.inc
include /etc/firejail/disable-common.inc
caps.drop all
seccomp
noroot

2
etc/icecat.profile Normal file
View file

@ -0,0 +1,2 @@
# Firejail profile for GNU Icecat
include /etc/firejail/firefox.profile

3
etc/icedove.profile Normal file
View file

@ -0,0 +1,3 @@
# Firejail profile for Mozilla Thunderbird (Icedove in Debian)
include /etc/firejail/thunderbird.profile

2
etc/iceweasel.profile Normal file
View file

@ -0,0 +1,2 @@
# Firejail profile for Mozilla Firefox (Iceweasel in Debian)
include /etc/firejail/firefox.profile

14
etc/login.users Normal file
View file

@ -0,0 +1,14 @@
# /etc/firejail/login.users - restricted user shell configuration
#
# Each user entry consists of a user name and firejail
# program arguments:
#
# user name: arguments
#
# For example:
#
# netblue:--debug --net=none
#
# The extra arguments are inserted into program command line if firejail
# was started as a login shell.

9
etc/midori.profile Normal file
View file

@ -0,0 +1,9 @@
# Midory browser profile
include /etc/firejail/disable-mgmt.inc
include /etc/firejail/disable-secret.inc
include /etc/firejail/disable-common.inc midori
caps.drop all
seccomp
netfilter
noroot

8
etc/opera.profile Normal file
View file

@ -0,0 +1,8 @@
# Chromium browser profile
include /etc/firejail/disable-mgmt.inc
include /etc/firejail/disable-secret.inc
include /etc/firejail/disable-common.inc opera
netfilter
noroot

7
etc/pidgin.profile Normal file
View file

@ -0,0 +1,7 @@
# Pidgin profile
include /etc/firejail/disable-mgmt.inc
include /etc/firejail/disable-secret.inc
include /etc/firejail/disable-common.inc
caps.drop all
seccomp
noroot

9
etc/qbittorrent.profile Normal file
View file

@ -0,0 +1,9 @@
# abittorrent profile
include /etc/firejail/disable-mgmt.inc
include /etc/firejail/disable-secret.inc
include /etc/firejail/disable-common.inc
caps.drop all
seccomp
netfilter
noroot

7
etc/quassel.profile Normal file
View file

@ -0,0 +1,7 @@
# Quassel IRC profile
include /etc/firejail/disable-mgmt.inc
include /etc/firejail/disable-secret.inc
include /etc/firejail/disable-common.inc
caps.drop all
seccomp
noroot

7
etc/rhythmbox.profile Normal file
View file

@ -0,0 +1,7 @@
# Rhythmbox profile
include /etc/firejail/disable-mgmt.inc
include /etc/firejail/disable-secret.inc
include /etc/firejail/disable-common.inc
caps.drop all
seccomp
noroot

6
etc/server.profile Normal file
View file

@ -0,0 +1,6 @@
# generic server profile
include /etc/firejail/disable-mgmt.inc sbin
private
private-dev
seccomp

9
etc/thunderbird.profile Normal file
View file

@ -0,0 +1,9 @@
# Firejail profile for Mozilla Thunderbird (Icedove in Debian)
include /etc/firejail/disable-mgmt.inc
include /etc/firejail/disable-secret.inc
include /etc/firejail/disable-common.inc thunderbird icedove
caps.drop all
seccomp
netfilter
noroot

7
etc/totem.profile Normal file
View file

@ -0,0 +1,7 @@
# Totem profile
include /etc/firejail/disable-mgmt.inc
include /etc/firejail/disable-secret.inc
include /etc/firejail/disable-common.inc
caps.drop all
seccomp
noroot

View file

@ -0,0 +1,9 @@
# transmission-gtk profile
include /etc/firejail/disable-mgmt.inc
include /etc/firejail/disable-secret.inc
include /etc/firejail/disable-common.inc
caps.drop all
seccomp
netfilter
noroot

View file

@ -0,0 +1,9 @@
# transmission-qt profile
include /etc/firejail/disable-mgmt.inc
include /etc/firejail/disable-secret.inc
include /etc/firejail/disable-common.inc
caps.drop all
seccomp
netfilter
noroot

7
etc/vlc.profile Normal file
View file

@ -0,0 +1,7 @@
# VLC profile
include /etc/firejail/disable-mgmt.inc
include /etc/firejail/disable-secret.inc
include /etc/firejail/disable-common.inc
caps.drop all
seccomp
noroot

7
etc/xchat.profile Normal file
View file

@ -0,0 +1,7 @@
# XChat profile
include /etc/firejail/disable-mgmt.inc
include /etc/firejail/disable-secret.inc
include /etc/firejail/disable-common.inc
caps.drop all
seccomp
noroot

2
install.sh Executable file
View file

@ -0,0 +1,2 @@
#!/bin/bash
echo "installing..."

96
mkdeb.sh Executable file
View file

@ -0,0 +1,96 @@
#!/bin/bash
# a code archive should already be available
TOP=`pwd`
CODE_ARCHIVE="$1-$2.tar.bz2"
CODE_DIR="$1-$2"
INSTALL_DIR=$TOP
INSTALL_DIR+="/debian/usr"
DEBIAN_CTRL_DIR=$TOP
DEBIAN_CTRL_DIR+="/debian/DEBIAN"
echo "*****************************************"
echo "code archive: $CODE_ARCHIVE"
echo "code directory: $CODE_DIR"
echo "install directory: $INSTALL_DIR"
echo "debian control directory: $DEBIAN_CTRL_DIR"
echo "*****************************************"
tar -xjvf $CODE_ARCHIVE
mkdir -p $INSTALL_DIR
cd $CODE_DIR
./configure --prefix=$INSTALL_DIR
make && make install
# second compilation - the path to libtrace.so is hardcoded in firejail executable
# pointing according to --prefix=$INSTALL_DIR. We need it to point to /usr/lib
make distclean
./configure --prefix=/usr
make
# install firejail executable in $TOP/$INSTALL_DIR
strip src/firejail/firejail
install -c -m 0755 src/firejail/firejail $INSTALL_DIR/bin/.
chmod u+s $INSTALL_DIR/bin/firejail
cd ..
echo "*****************************************"
SIZE=`du -s debian/usr`
echo "install size $SIZE"
echo "*****************************************"
mv $INSTALL_DIR/share/doc/firejail/RELNOTES $INSTALL_DIR/share/doc/firejail/changelog.Debian
gzip -9 $INSTALL_DIR/share/doc/firejail/changelog.Debian
rm $INSTALL_DIR/share/doc/firejail/COPYING
cp platform/debian/copyright $INSTALL_DIR/share/doc/firejail/.
mkdir -p $DEBIAN_CTRL_DIR
sed "s/FIREJAILVER/$2/g" platform/debian/control > $DEBIAN_CTRL_DIR/control
mkdir -p debian/etc/firejail
cp etc/chromium.profile debian/etc/firejail/.
cp etc/chromium-browser.profile debian/etc/firejail/.
cp etc/disable-mgmt.inc debian/etc/firejail/.
cp etc/disable-secret.inc debian/etc/firejail/.
cp etc/dropbox.profile debian/etc/firejail/.
cp etc/evince.profile debian/etc/firejail/.
cp etc/firefox.profile debian/etc/firejail/.
cp etc/iceweasel.profile debian/etc/firejail/.
cp etc/icedove.profile debian/etc/firejail/.
cp etc/login* debian/etc/firejail/.
cp etc/midori.profile debian/etc/firejail/.
cp etc/opera.profile debian/etc/firejail/.
cp etc/thunderbird.profile debian/etc/firejail/.
cp etc/transmission-gtk.profile debian/etc/firejail/.
cp etc/transmission-qt.profile debian/etc/firejail/.
cp etc/vlc.profile debian/etc/firejail/.
cp etc/audacious.profile debian/etc/firejail/.
cp etc/clementine.profile debian/etc/firejail/.
cp etc/gnome-mplayer.profile debian/etc/firejail/.
cp etc/rhythmbox.profile debian/etc/firejail/.
cp etc/totem.profile debian/etc/firejail/.
cp etc/deluge.profile debian/etc/firejail/.
cp etc/qbittorrent.profile debian/etc/firejail/.
cp etc/generic.profile debian/etc/firejail/.
cp etc/xchat.profile debian/etc/firejail/.
cp etc/server.profile debian/etc/firejail/.
cp etc/quassel.profile debian/etc/firejail/.
cp etc/pidgin.profile debian/etc/firejail/.
cp etc/filezilla.profile debian/etc/firejail/.
cp etc/empathy.profile debian/etc/firejail/.
cp etc/disable-common.inc debian/etc/firejail/.
cp etc/deadbeef.profile debian/etc/firejail/.
cp etc/icecat.profile debian/etc/firejail/.
cp platform/debian/conffiles $DEBIAN_CTRL_DIR/.
find ./debian -type d | xargs chmod 755
dpkg-deb --build debian
lintian debian.deb
mv debian.deb firejail_$2_1_amd64.deb
echo "if building a 32bit package, rename the deb file manually"
rm -fr debian
rm -fr $CODE_DIR

7
mkman.sh Executable file
View file

@ -0,0 +1,7 @@
#!/bin/bash
sed "s/VERSION/$1/g" $2 > $3
MONTH=`date +%b`
sed -i "s/MONTH/$MONTH/g" $3
YEAR=`date +%Y`
sed -i "s/YEAR/$YEAR/g" $3

33
platform/debian/conffiles Normal file
View file

@ -0,0 +1,33 @@
/etc/firejail/evince.profile
/etc/firejail/disable-secret.inc
/etc/firejail/chromium.profile
/etc/firejail/midori.profile
/etc/firejail/icedove.profile
/etc/firejail/iceweasel.profile
/etc/firejail/dropbox.profile
/etc/firejail/login.users
/etc/firejail/chromium-browser.profile
/etc/firejail/disable-mgmt.inc
/etc/firejail/firefox.profile
/etc/firejail/opera.profile
/etc/firejail/thunderbird.profile
/etc/firejail/transmission-gtk.profile
/etc/firejail/transmission-qt.profile
/etc/firejail/vlc.profile
/etc/firejail/audacious.profile
/etc/firejail/clementine.profile
/etc/firejail/gnome-mplayer.profile
/etc/firejail/rhythmbox.profile
/etc/firejail/totem.profile
/etc/firejail/deluge.profile
/etc/firejail/qbittorrent.profile
/etc/firejail/generic.profile
/etc/firejail/xchat.profile
/etc/firejail/server.profile
/etc/firejail/quassel.profile
/etc/firejail/pidgin.profile
/etc/firejail/filezilla.profile
/etc/firejail/empathy.profile
/etc/firejail/disable-common.inc
/etc/firejail/deadbeef.profile
/etc/firejail/icecat.profile

20
platform/debian/control Normal file
View file

@ -0,0 +1,20 @@
Package: firejail
Version: FIREJAILVER-1
Architecture: amd64
Maintainer: netblue30 <netblue30@yahoo.com>
Installed-Size: 272
Depends: libc6
Section: admin
Priority: extra
Homepage: http://firejail.sourceforge.net
Description: Linux namepaces sandbox program.
Firejail is a SUID sandbox program that reduces the risk of security
breaches by restricting the running environment of untrusted applications
using Linux namespaces and seccmp-bpf. It includes sandbox profiles for
Iceweasel/Mozilla Firefox, Chromium, Midori, Opera, Evince, Transmission
and VLC.
.
Firejail also expands the restricted shell facility found in bash by
adding Linux namespace support. It also supports sandboxing SSH users
upon login.

30
platform/debian/copyright Normal file
View file

@ -0,0 +1,30 @@
This is the Debian/Ubuntu prepackaged version of firejail.
Firejail is a sandbox program that reduces the risk of security breaches
by restricting the running environment of untrusted applications using
Linux namespaces. It currently implements hostname, filesystem, PID, IPC
and networking stack isolation, and it runs on any recent Linux system. It
includes a sandbox profile for Mozilla Firefox.
Copyright (C) 2014,2015 Firejail Authors (see README file for more details)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc., 51
Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
The complete text of the GNU General Public License can be found
in /usr/share/common-licenses/GPL-2.
Homepage: http://firejail.sourceforge.net.

256
platform/rpm/mkrpm.sh Executable file
View file

@ -0,0 +1,256 @@
#!/bin/bash
VERSION="0.9.26"
rm -fr ~/rpmbuild
rm -f firejail-$VERSION-1.x86_64.rpm
mkdir -p ~/rpmbuild/{RPMS,SRPMS,BUILD,SOURCES,SPECS,tmp}
cat <<EOF >~/.rpmmacros
%_topdir %(echo $HOME)/rpmbuild
%_tmppath %{_topdir}/tmp
EOF
cd ~/rpmbuild
echo "building directory tree"
mkdir -p firejail-$VERSION/usr/bin
install -m 755 /usr/bin/firejail firejail-$VERSION/usr/bin/.
install -m 755 /usr/bin/firemon firejail-$VERSION/usr/bin/.
mkdir -p firejail-$VERSION/usr/lib/firejail
install -m 644 /usr/lib/firejail/libtrace.so firejail-$VERSION/usr/lib/firejail/.
install -m 755 /usr/lib/firejail/ftee firejail-$VERSION/usr/lib/firejail/.
mkdir -p firejail-$VERSION/usr/share/man/man1
install -m 644 /usr/share/man/man1/firejail.1.gz firejail-$VERSION/usr/share/man/man1/.
install -m 644 /usr/share/man/man1/firemon.1.gz firejail-$VERSION/usr/share/man/man1/.
mkdir -p firejail-$VERSION/usr/share/man/man5
install -m 644 /usr/share/man/man5/firejail-profile.5.gz firejail-$VERSION/usr/share/man/man5/.
mkdir -p firejail-$VERSION/usr/share/doc/packages/firejail
install -m 644 /usr/share/doc/firejail/COPYING firejail-$VERSION/usr/share/doc/packages/firejail/.
install -m 644 /usr/share/doc/firejail/README firejail-$VERSION/usr/share/doc/packages/firejail/.
install -m 644 /usr/share/doc/firejail/RELNOTES firejail-$VERSION/usr/share/doc/packages/firejail/.
mkdir -p firejail-$VERSION/etc/firejail
install -m 644 /etc/firejail/chromium-browser.profile firejail-$VERSION/etc/firejail/chromium-browser.profile
install -m 644 /etc/firejail/chromium.profile firejail-$VERSION/etc/firejail/chromium.profile
install -m 644 /etc/firejail/dropbox.profile firejail-$VERSION/etc/firejail/dropbox.profile
install -m 644 /etc/firejail/disable-secret.inc firejail-$VERSION/etc/firejail/disable-secret.inc
install -m 644 /etc/firejail/disable-mgmt.inc firejail-$VERSION/etc/firejail/disable-mgmt.inc
install -m 644 /etc/firejail/evince.profile firejail-$VERSION/etc/firejail/evince.profile
install -m 644 /etc/firejail/firefox.profile firejail-$VERSION/etc/firejail/firefox.profile
install -m 644 /etc/firejail/icedove.profile firejail-$VERSION/etc/firejail/icedove.profile
install -m 644 /etc/firejail/iceweasel.profile firejail-$VERSION/etc/firejail/iceweasel.profile
install -m 644 /etc/firejail/midori.profile firejail-$VERSION/etc/firejail/midori.profile
install -m 644 /etc/firejail/thunderbird.profile firejail-$VERSION/etc/firejail/thunderbird.profile
install -m 644 /etc/firejail/opera.profile firejail-$VERSION/etc/firejail/opera.profile
install -m 644 /etc/firejail/transmission-gtk.profile firejail-$VERSION/etc/firejail/transmission-gtk.profile
install -m 644 /etc/firejail/transmission-qt.profile firejail-$VERSION/etc/firejail/transmission-qt.profile
install -m 644 /etc/firejail/vlc.profile firejail-$VERSION/etc/firejail/vlc.profile
install -m 644 /etc/firejail/audacious.profile firejail-$VERSION/etc/firejail/audacious.profile
install -m 644 /etc/firejail/clementine.profile firejail-$VERSION/etc/firejail/clementine.profile
install -m 644 /etc/firejail/gnome-mplayer.profile firejail-$VERSION/etc/firejail/gnome-mplayer.profile
install -m 644 /etc/firejail/rhythmbox.profile firejail-$VERSION/etc/firejail/rhythmbox.profile
install -m 644 /etc/firejail/totem.profile firejail-$VERSION/etc/firejail/totem.profile
install -m 644 /etc/firejail/deluge.profile firejail-$VERSION/etc/firejail/deluge.profile
install -m 644 /etc/firejail/qbittorrent.profile firejail-$VERSION/etc/firejail/qbittorrent.profile
install -m 644 /etc/firejail/generic.profile firejail-$VERSION/etc/firejail/generic.profile
install -m 644 /etc/firejail/login.users firejail-$VERSION/etc/firejail/login.users
mkdir -p firejail-$VERSION/usr/share/bash-completion/completions
install -m 644 /usr/share/bash-completion/completions/firejail firejail-$VERSION/usr/share/bash-completion/completions/.
echo "building tar.gz archive"
tar -czvf firejail-$VERSION.tar.gz firejail-$VERSION
cp firejail-$VERSION.tar.gz SOURCES/.
echo "building config spec"
cat <<EOF > SPECS/firejail.spec
%define __spec_install_post %{nil}
%define debug_package %{nil}
%define __os_install_post %{_dbpath}/brp-compress
Summary: Linux namepaces sandbox program
Name: firejail
Version: $VERSION
Release: 1
License: GPL+
Group: Development/Tools
SOURCE0 : %{name}-%{version}.tar.gz
URL: http://firejail.sourceforege.net
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
%description
Firejail is a SUID sandbox program that reduces the risk of security
breaches by restricting the running environment of untrusted applications
using Linux namespaces. It includes a sandbox profile for Mozilla Firefox.
%prep
%setup -q
%build
%install
rm -rf %{buildroot}
mkdir -p %{buildroot}
cp -a * %{buildroot}
%clean
rm -rf %{buildroot}
%files
%defattr(-,root,root,-)
%config(noreplace) %{_sysconfdir}/%{name}/chromium-browser.profile
%config(noreplace) %{_sysconfdir}/%{name}/chromium.profile
%config(noreplace) %{_sysconfdir}/%{name}/disable-mgmt.inc
%config(noreplace) %{_sysconfdir}/%{name}/disable-secret.inc
%config(noreplace) %{_sysconfdir}/%{name}/dropbox.profile
%config(noreplace) %{_sysconfdir}/%{name}/evince.profile
%config(noreplace) %{_sysconfdir}/%{name}/firefox.profile
%config(noreplace) %{_sysconfdir}/%{name}/icedove.profile
%config(noreplace) %{_sysconfdir}/%{name}/iceweasel.profile
%config(noreplace) %{_sysconfdir}/%{name}/login.users
%config(noreplace) %{_sysconfdir}/%{name}/midori.profile
%config(noreplace) %{_sysconfdir}/%{name}/opera.profile
%config(noreplace) %{_sysconfdir}/%{name}/thunderbird.profile
%config(noreplace) %{_sysconfdir}/%{name}/transmission-gtk.profile
%config(noreplace) %{_sysconfdir}/%{name}/transmission-qt.profile
%config(noreplace) %{_sysconfdir}/%{name}/vlc.profile
%config(noreplace) %{_sysconfdir}/%{name}/audacious.profile
%config(noreplace) %{_sysconfdir}/%{name}/clementine.profile
%config(noreplace) %{_sysconfdir}/%{name}/gnome-mplayer.profile
%config(noreplace) %{_sysconfdir}/%{name}/rhythmbox.profile
%config(noreplace) %{_sysconfdir}/%{name}/totem.profile
%config(noreplace) %{_sysconfdir}/%{name}/deluge.profile
%config(noreplace) %{_sysconfdir}/%{name}/qbittorrent.profile
%config(noreplace) %{_sysconfdir}/%{name}/generic.profile
/usr/bin/firejail
/usr/bin/firemon
/usr/lib/firejail/libtrace.so
/usr/lib/firejail/ftee
/usr/share/doc/packages/firejail/COPYING
/usr/share/doc/packages/firejail/README
/usr/share/doc/packages/firejail/RELNOTES
/usr/share/man/man1/firejail.1.gz
/usr/share/man/man1/firemon.1.gz
/usr/share/man/man5/firejail-profile.5.gz
/usr/share/bash-completion/completions/firejail
%post
chmod u+s /usr/bin/firejail
%changelog
* Thu Apr 30 2015 netblue30 <netblue30@yahoo.com> 0.9.26-1
- private dev directory
- private.keep option for whitelisting home files in a new private directory
- user namespaces support, noroot option
- added Deluge and qBittorent profiles
- bugfixes
* Sun Apr 5 2015 netblue30 <netblue30@yahoo.com> 0.9.24-1
- whitelist and blacklist seccomp filters
- doubledash option
- --shell=none support
- netfilter file support in profile files
- dns server support in profile files
- added --dns.print option
- added default profiles for Audoacious, Clementine, Rhythmbox and Totem.
- added --caps.drop=all in default profiles
- new syscalls in default seccomp filter: sysfs, sysctl, adjtimex, kcmp
- clock_adjtime, lookup_dcookie, perf_event_open, fanotify_init
- Bugfix: using /proc/sys/kernel/pid_max for the max number of pids
- two build patches from Reiner Herman (tickets 11, 12)
- man page patch from Reiner Herman (ticket 13)
- output patch (ticket 15) from sshirokov
* Mon Mar 9 2015 netblue30 <netblue30@yahoo.com> 0.9.22-1
- Replaced --noip option with --ip=none
- Container stdout logging and log rotation
- Added process_vm_readv, process_vm_writev and mknod to
default seccomp blacklist
- Added CAP_MKNOD to default caps blacklist
- Blacklist and whitelist custom Linux capabilities filters
- macvlan device driver support for --net option
- DNS server support, --dns option
- Netfilter support
- Monitor network statistics, --netstats option
- Added profile for Mozilla Thunderbird/Icedove
- --overlay support for Linux kernels 3.18+
- Bugfix: preserve .Xauthority file in private mode (test with ssh -X)
- Bugfix: check uid/gid for cgroup
* Fri Feb 6 2015 netblue30 <netblue30@yahoo.com> 0.9.20-1
- utmp, btmp and wtmp enhancements
- create empty /var/log/wtmp and /var/log/btmp files in sandbox
- generate a new /var/run/utmp file in sandbox
- CPU affinity, --cpu option
- Linux control groups support, --cgroup option
- Opera web browser support
- VLC support
- Added "empty" attribute to seccomp command to remove the default
- syscall list form seccomp blacklist
- Added --nogroups option to disable supplementary groups for regular
- users. root user always runs without supplementary groups.
- firemon enhancements
- display the command that started the sandbox
- added --caps option to display capabilities for all sandboxes
- added --cgroup option to display the control groups for all sandboxes
- added --cpu option to display CPU affinity for all sandboxes
- added --seccomp option to display seccomp setting for all sandboxes
- New compile time options: --disable-chroot, --disable-bind
- bugfixes
* Sat Dec 27 2014 netblue30 <netblue30@yahoo.com> 0.9.18-1
- Support for tracing system, setuid, setgid, setfsuid, setfsgid syscalls
- Support for tracing setreuid, setregid, setresuid, setresguid syscalls
- Added profiles for transmission-gtk and transmission-qt
- bugfixes
* Tue Nov 4 2014 netblue30 <netblue30@yahoo.com> 0.9.16-1
- Configurable private home directory
- Configurable default user shell
- Software configuration support for --docdir and DESTDIR
- Profile file support for include, caps, seccomp and private keywords
- Dropbox profile file
- Linux capabilities and seccomp filters enabled by default for Firefox,
Midori, Evince and Dropbox
- bugfixes
* Wed Oct 8 2014 netblue30 <netblue30@yahoo.com> 0.9.14-1
- Linux capabilities and seccomp filters are automatically enabled in
chroot mode (--chroot option) if the sandbox is started as regular
user
- Added support for user defined seccomp blacklists
- Added syscall trace support
- Added --tmpfs option
- Added --balcklist option
- Added --read-only option
- Added --bind option
- Logging enhancements
- --overlay option was reactivated
- Added firemon support to print the ARP table for each sandbox
- Added firemon support to print the route table for each sandbox
- Added firemon support to print interface information for each sandbox
- bugfixes
* Tue Sep 16 2014 netblue30 <netblue30@yahoo.com> 0.9.12-1
- Added capabilities support
- Added support for CentOS 7
- bugfixes
EOF
echo "building rpm"
rpmbuild -ba SPECS/firejail.spec
rpm -qpl RPMS/x86_64/firejail-$VERSION-1.x86_64.rpm
cd ..
rm -f firejail-$VERSION-1.x86_64.rpm
cp rpmbuild/RPMS/x86_64/firejail-$VERSION-1.x86_64.rpm .

28
src/firejail/Makefile.in Normal file
View file

@ -0,0 +1,28 @@
all: firejail
PREFIX=@prefix@
VERSION=@PACKAGE_VERSION@
NAME=@PACKAGE_NAME@
HAVE_SECCOMP_H=@HAVE_SECCOMP_H@
HAVE_SECCOMP=@HAVE_SECCOMP@
HAVE_CHROOT=@HAVE_CHROOT@
HAVE_BIND=@HAVE_BIND@
H_FILE_LIST = $(wildcard *.[h])
C_FILE_LIST = $(wildcard *.c)
OBJS = $(C_FILE_LIST:.c=.o)
BINOBJS = $(foreach file, $(OBJS), $file)
CFLAGS += -ggdb -O2 -DVERSION='"$(VERSION)"' -DPREFIX='"$(PREFIX)"' $(HAVE_SECCOMP) $(HAVE_SECCOMP_H) $(HAVE_CHROOT) $(HAVE_BIND) -fstack-protector-all -D_FORTIFY_SOURCE=2 -fPIE -pie -Wformat -Wformat-security
LDFLAGS += -pie -Wl,-z,relro -Wl,-z,now -lpthread
%.o : %.c $(H_FILE_LIST)
$(CC) $(CFLAGS) $(INCLUDE) -c $< -o $@
firejail: $(OBJS) ../lib/libnetlink.o ../lib/common.o
$(CC) $(LDFLAGS) -o $@ $(OBJS) ../lib/libnetlink.o ../lib/common.o $(LIBS)
clean:; rm -f *.o firejail firejail.1 firejail.1.gz
distclean: clean
rm -fr Makefile

View file

@ -0,0 +1,85 @@
arg checking:
1. --output=filename
- not supported in profiles
- checking no "..",
- checking no link,
- checking no dir,
- checking same permissions,
- checking no hard links
- unit test
2. --chroot=dirname
- not supported in profiles
- expand "~"
- checking no "..",
- checking is dir,
- checking no link
- checking directory structure
- unit test
3. --bind=dirname1,dirname2, --bind=filename1,filenam2
- supported in profiles
- accepted only when running as root
- checking string chars
- checking no ".."
- unit test non root
4. --tmpfs=dirname
- supported in profiles
- checking string chars
- checking no ".."
- unit test
5. --blacklist=filename, --blacklist=dirname
- supported in profiles
- checking string chars
- checking no ".."
- unit test
6. --read-only=filename, --read-only=dirname
- supported in profiles
- checking string chars
- checking no ".."
- unit test
7. --profile=filename
- check access as real GID/UID
- checking no dir
- checking no link
- checking no ".."
- unit test
8. --private=dirname
- supported in profiles
- expand "~"
- check is dir
- check no link
- checking no ".."
- check same owner
- unit test
9. --private.keep=filelist
- supported in profiles
- checking no ".."
- checking file found
- checking same owner
- checking no link
- unit test
10. --netfilter=filename
- supported in profiles
- check access as real GID/UID
- checking no dir
- checking no link
- checking no ".."
- unit test
11. --shell=filename
- not supported in profiles
- check access as real GID/UID
- checking no dir
- checking no link
- checking no ".."
- unit test

474
src/firejail/arp.c Normal file
View file

@ -0,0 +1,474 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firejail.h"
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <linux/if_ether.h> //TCP/IP Protocol Suite for Linux
#include <net/if.h>
#include <netinet/in.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <linux/tcp.h>
#include <linux/if_packet.h>
typedef struct arp_hdr_t {
uint16_t htype;
uint16_t ptype;
uint8_t hlen;
uint8_t plen;
uint16_t opcode;
uint8_t sender_mac[6];
uint8_t sender_ip[4];
uint8_t target_mac[6];
uint8_t target_ip[4];
} ArpHdr;
// returns 0 if the address is not in use, -1 otherwise
int arp_check(const char *dev, uint32_t destaddr, uint32_t srcaddr) {
if (strlen(dev) > IFNAMSIZ) {
fprintf(stderr, "Error: invalid network device name %s\n", dev);
exit(1);
}
if (arg_debug)
printf("Trying %d.%d.%d.%d ...\n", PRINT_IP(destaddr));
// find interface address
int sock;
if ((sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0)
errExit("socket");
srcaddr = htonl(srcaddr);
destaddr = htonl(destaddr);
// Find interface MAC address
struct ifreq ifr;
memset(&ifr, 0, sizeof (ifr));
strncpy(ifr.ifr_name, dev, IFNAMSIZ);
if (ioctl(sock, SIOCGIFHWADDR, &ifr) < 0)
errExit("ioctl");
close(sock);
// configure layer2 socket address information
struct sockaddr_ll addr;
memset(&addr, 0, sizeof(addr));
if ((addr.sll_ifindex = if_nametoindex(dev)) == 0)
errExit("if_nametoindex");
addr.sll_family = AF_PACKET;
memcpy (addr.sll_addr, ifr.ifr_hwaddr.sa_data, 6);
addr.sll_halen = htons(6);
// build the arp packet header
ArpHdr hdr;
memset(&hdr, 0, sizeof(hdr));
hdr.htype = htons(1);
hdr.ptype = htons(ETH_P_IP);
hdr.hlen = 6;
hdr.plen = 4;
hdr.opcode = htons(1); //ARPOP_REQUEST
memcpy(hdr.sender_mac, ifr.ifr_hwaddr.sa_data, 6);
memcpy(hdr.sender_ip, (uint8_t *)&srcaddr, 4);
memcpy(hdr.target_ip, (uint8_t *)&destaddr, 4);
// buiild ethernet frame
uint8_t frame[ETH_FRAME_LEN]; // includes eht header, vlan, and crc
memset(frame, 0, sizeof(frame));
frame[0] = frame[1] = frame[2] = frame[3] = frame[4] = frame[5] = 0xff;
memcpy(frame + 6, ifr.ifr_hwaddr.sa_data, 6);
frame[12] = ETH_P_ARP / 256;
frame[13] = ETH_P_ARP % 256;
memcpy (frame + 14, &hdr, sizeof(hdr));
// open layer2 socket
if ((sock = socket(PF_PACKET, SOCK_RAW, htons (ETH_P_ALL))) < 0)
errExit("socket");
int len;
if ((len = sendto (sock, frame, 14 + sizeof(ArpHdr), 0, (struct sockaddr *) &addr, sizeof (addr))) <= 0)
errExit("send");
fflush(0);
// wait not more than one second for an answer
fd_set fds;
FD_ZERO(&fds);
FD_SET(sock, &fds);
int maxfd = sock;
struct timeval ts;
ts.tv_sec = 1; // 1 second wait time
ts.tv_usec = 0;
while (1) {
int nready = select(maxfd + 1, &fds, (fd_set *) 0, (fd_set *) 0, &ts);
if (nready < 0)
errExit("select");
else if (nready == 0) { // timeout
close(sock);
return 0;
}
else {
// read the incoming packet
int len = recvfrom(sock, frame, ETH_FRAME_LEN, 0, NULL, NULL);
if (len < 0) {
perror("recvfrom");
close(sock);
return -1;
}
// parse the incomming packet
if (len < 14 + sizeof(ArpHdr))
continue;
if (frame[12] != (ETH_P_ARP / 256) || frame[13] != (ETH_P_ARP % 256))
continue;
memcpy(&hdr, frame + 14, sizeof(ArpHdr));
if (hdr.opcode == htons(1))
continue;
if (hdr.opcode == htons(2)) {
// check my mac and my address
if (memcmp(ifr.ifr_hwaddr.sa_data, hdr.target_mac, 6) != 0)
continue;
uint32_t ip;
memcpy(&ip, hdr.target_ip, 4);
if (ip != srcaddr) {
continue;
}
close(sock);
return -1;
}
}
}
// it will never get here!
close(sock);
return -1;
}
// assign a random IP address and check it
// the address needs to be in the range if it --iprange was specified
static uint32_t arp_random(const char *dev, Bridge *br) {
assert(dev);
assert(br);
uint32_t ifip = br->ip;
uint32_t ifmask = br->mask;
assert(ifip);
assert(ifmask);
if (arg_debug)
printf("ARP-scan %s, %d.%d.%d.%d/%d\n",
dev, PRINT_IP(ifip), mask2bits(ifmask));
// determine the range based on network address
uint32_t range = ~ifmask + 1; // the number of potential addresses
// this software is not supported for /31 networks
if (range < 4)
return 0; // the user will have to set the IP address manually
range -= 2; // subtract the network address and the broadcast address
uint32_t start = (ifip & ifmask) + 1;
// adjust range based on --iprange params
if (br->iprange_start && br->iprange_end) {
start = br->iprange_start;
range = br->iprange_end - br->iprange_start;
}
if (arg_debug)
printf("IP address range from %d.%d.%d.%d to %d.%d.%d.%d\n",
PRINT_IP(start), PRINT_IP(start + range));
// generate a random address - 10 tries
uint32_t dest = 0;
int i = 0;
for (i = 0; i < 10; i++) {
dest = start + ((uint32_t) rand()) % range;
if (dest == ifip) // do not allow the interface address
continue; // try again
// if we've made it up to here, we have a valid address
break;
}
if (i == 10) // we failed 10 times
return 0;
// check address
uint32_t rv = arp_check(dev, dest, ifip);
if (!rv)
return dest;
return 0;
}
// go sequentially trough all IP addresses and assign the first one not in use
static uint32_t arp_sequential(const char *dev, Bridge *br) {
assert(dev);
assert(br);
uint32_t ifip = br->ip;
uint32_t ifmask = br->mask;
assert(ifip);
assert(ifmask);
// range based on network address
uint32_t range = ~ifmask + 1; // the number of potential addresses
// this software is not supported for /31 networks
if (range < 4)
return 0; // the user will have to set the IP address manually
range -= 2; // subtract the network address and the broadcast address
// try all possible ip addresses in ascending order
// start address
uint32_t dest = (ifip & ifmask) + 1;
if (br->iprange_start)
dest = br->iprange_start;
// end address
uint32_t last = dest + range - 1;
if (br->iprange_end)
last = br->iprange_end;
if (arg_debug)
printf("Trying IP address range from %d.%d.%d.%d to %d.%d.%d.%d\n",
PRINT_IP(dest), PRINT_IP(last));
// loop through addresses and stop as soon as you find an unused one
while (dest <= last) {
if (dest == ifip) {
dest++;
continue;
}
uint32_t rv = arp_check(dev, dest, ifip);
if (!rv)
return dest;
dest++;
}
return 0;
}
// assign an IP address first trying some random addresses, and if this fails
// by doing an arp scan.
//
// dev is the name of the device to use in scanning,
// br is bridge structure holding the ip address and mask to use in
// arp packets. It also holds values for for the range of addresses
// if --iprange was set by the user
uint32_t arp_assign(const char *dev, Bridge *br) {
assert(br);
uint32_t ip = 0;
// try two random IP addresses
ip = arp_random(dev, br);
if (!ip)
ip = arp_random(dev, br);
// try all possible IP addresses one by one
if (!ip)
ip = arp_sequential(dev, br);
// print result
if (!ip) {
fprintf(stderr, "Error: cannot assign an IP address; it looks like all of them are in use.\n");
logerr("Cannot assign an IP address; it looks like all of them are in use.");
exit(1);
}
return ip;
}
// scan interface (--scan option)
void arp_scan(const char *dev, uint32_t ifip, uint32_t ifmask) {
assert(dev);
assert(ifip);
// printf("Scanning interface %s (%d.%d.%d.%d/%d)\n",
// dev, PRINT_IP(ifip & ifmask), mask2bits(ifmask));
if (strlen(dev) > IFNAMSIZ) {
fprintf(stderr, "Error: invalid network device name %s\n", dev);
exit(1);
}
// find interface mac address
int sock;
if ((sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0)
errExit("socket");
struct ifreq ifr;
memset(&ifr, 0, sizeof (ifr));
strncpy(ifr.ifr_name, dev, IFNAMSIZ);
if (ioctl(sock, SIOCGIFHWADDR, &ifr) < 0)
errExit("ioctl");
close(sock);
uint8_t mac[6];
memcpy (mac, ifr.ifr_hwaddr.sa_data, 6);
// open layer2 socket
if ((sock = socket(PF_PACKET, SOCK_RAW, htons (ETH_P_ALL))) < 0)
errExit("socket");
// try all possible ip addresses in ascending order
uint32_t range = ~ifmask + 1; // the number of potential addresses
// this software is not supported for /31 networks
if (range < 4) {
fprintf(stderr, "Warning: this option is not supported for /31 networks\n");
close(sock);
return;
}
uint32_t dest = (ifip & ifmask) + 1;
uint32_t last = dest + range - 1;
uint32_t src = htonl(ifip);
// wait not more than one second for an answer
int header_printed = 0;
int last_ip = 0;
struct timeval ts;
ts.tv_sec = 2; // 2 seconds receive timeout
ts.tv_usec = 0;
while (1) {
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(sock, &rfds);
fd_set wfds;
FD_ZERO(&wfds);
FD_SET(sock, &wfds);
int maxfd = sock;
uint8_t frame[ETH_FRAME_LEN]; // includes eht header, vlan, and crc
memset(frame, 0, ETH_FRAME_LEN);
int nready;
if (dest < last)
nready = select(maxfd + 1, &rfds, &wfds, (fd_set *) 0, NULL);
else
nready = select(maxfd + 1, &rfds, (fd_set *) 0, (fd_set *) 0, &ts);
if (nready < 0)
errExit("select");
if (nready == 0) { // timeout
break;
}
if (FD_ISSET(sock, &wfds) && dest < last) {
// configure layer2 socket address information
struct sockaddr_ll addr;
memset(&addr, 0, sizeof(addr));
if ((addr.sll_ifindex = if_nametoindex(dev)) == 0)
errExit("if_nametoindex");
addr.sll_family = AF_PACKET;
memcpy (addr.sll_addr, mac, 6);
addr.sll_halen = htons(6);
// build the arp packet header
ArpHdr hdr;
memset(&hdr, 0, sizeof(hdr));
hdr.htype = htons(1);
hdr.ptype = htons(ETH_P_IP);
hdr.hlen = 6;
hdr.plen = 4;
hdr.opcode = htons(1); //ARPOP_REQUEST
memcpy(hdr.sender_mac, mac, 6);
memcpy(hdr.sender_ip, (uint8_t *)&src, 4);
uint32_t dst = htonl(dest);
memcpy(hdr.target_ip, (uint8_t *)&dst, 4);
// buiild ethernet frame
uint8_t frame[ETH_FRAME_LEN]; // includes eht header, vlan, and crc
memset(frame, 0, sizeof(frame));
frame[0] = frame[1] = frame[2] = frame[3] = frame[4] = frame[5] = 0xff;
memcpy(frame + 6, mac, 6);
frame[12] = ETH_P_ARP / 256;
frame[13] = ETH_P_ARP % 256;
memcpy (frame + 14, &hdr, sizeof(hdr));
// send packet
int len;
if ((len = sendto (sock, frame, 14 + sizeof(ArpHdr), 0, (struct sockaddr *) &addr, sizeof (addr))) <= 0)
errExit("send");
//printf("send %d bytes to %d.%d.%d.%d\n", len, PRINT_IP(dest));
fflush(0);
dest++;
}
if (FD_ISSET(sock, &rfds)) {
// read the incoming packet
int len = recvfrom(sock, frame, ETH_FRAME_LEN, 0, NULL, NULL);
if (len < 0) {
perror("recvfrom");
}
// parse the incomming packet
if (len < 14 + sizeof(ArpHdr))
continue;
// look only at ARP packets
if (frame[12] != (ETH_P_ARP / 256) || frame[13] != (ETH_P_ARP % 256))
continue;
ArpHdr hdr;
memcpy(&hdr, frame + 14, sizeof(ArpHdr));
if (hdr.opcode == htons(2)) {
// check my mac and my address
if (memcmp(mac, hdr.target_mac, 6) != 0)
continue;
uint32_t ip;
memcpy(&ip, hdr.target_ip, 4);
if (ip != src)
continue;
memcpy(&ip, hdr.sender_ip, 4);
ip = ntohl(ip);
if (ip == last_ip) // filter duplicates
continue;
last_ip = ip;
// printing
if (header_printed == 0) {
printf(" Network scan:\n");
// print parent interface
if (cfg.bridge0.configured && cfg.bridge0.ip && cfg.bridge0.macvlan &&
(cfg.bridge0.ip & cfg.bridge0.mask) == (ifip & cfg.bridge0.mask))
printf(" %02x:%02x:%02x:%02x:%02x:%02x\t%d.%d.%d.%d\n",
PRINT_MAC(cfg.bridge0.mac), PRINT_IP(cfg.bridge0.ip));
if (cfg.bridge1.configured && cfg.bridge1.ip && cfg.bridge1.macvlan &&
(cfg.bridge1.ip & cfg.bridge1.mask) == (ifip & cfg.bridge1.mask))
printf(" %02x:%02x:%02x:%02x:%02x:%02x\t%d.%d.%d.%d\n",
PRINT_MAC(cfg.bridge1.mac), PRINT_IP(cfg.bridge1.ip));
if (cfg.bridge2.configured && cfg.bridge2.ip && cfg.bridge2.macvlan &&
(cfg.bridge2.ip & cfg.bridge2.mask) == (ifip & cfg.bridge2.mask))
printf(" %02x:%02x:%02x:%02x:%02x:%02x\t%d.%d.%d.%d\n",
PRINT_MAC(cfg.bridge2.mac), PRINT_IP(cfg.bridge2.ip));
if (cfg.bridge3.configured && cfg.bridge3.ip && cfg.bridge3.macvlan &&
(cfg.bridge3.ip & cfg.bridge3.mask) == (ifip & cfg.bridge3.mask))
printf(" %02x:%02x:%02x:%02x:%02x:%02x\t%d.%d.%d.%d\n",
PRINT_MAC(cfg.bridge3.mac), PRINT_IP(cfg.bridge3.ip));
header_printed = 1;
}
printf(" %02x:%02x:%02x:%02x:%02x:%02x\t%d.%d.%d.%d\n",
PRINT_MAC(hdr.sender_mac), PRINT_IP(ip));
}
}
}
close(sock);
}

483
src/firejail/bandwidth.c Normal file
View file

@ -0,0 +1,483 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <net/if.h>
#include "firejail.h"
//***********************************
// interface bandwidth linked list
//***********************************
typedef struct ifbw_t {
struct ifbw_t *next;
char *txt;
} IFBW;
IFBW *ifbw = NULL;
#if 0
static void ifbw_print(void) {
IFBW *ptr = ifbw;
while (ptr) {
printf("#%s#\n", ptr->txt);
ptr = ptr->next;
}
}
#endif
static void ifbw_add(IFBW *ptr) {
assert(ptr);
if (ifbw != NULL)
ptr->next = ifbw;
ifbw = ptr;
}
IFBW *ifbw_find(const char *dev) {
assert(dev);
int len = strlen(dev);
assert(len);
if (ifbw == NULL)
return NULL;
IFBW *ptr = ifbw;
while (ptr) {
if (strncmp(ptr->txt, dev, len) == 0 && ptr->txt[len] == ':')
return ptr;
ptr = ptr->next;
}
return NULL;
}
void ifbw_remove(IFBW *r) {
if (ifbw == NULL)
return;
// remove the first element
if (ifbw == r) {
ifbw = ifbw->next;
return;
}
// walk the list
IFBW *ptr = ifbw->next;
IFBW *prev = ifbw;
while (ptr) {
if (ptr == r) {
prev->next = ptr->next;
return;
}
prev = ptr;
ptr = ptr->next;
}
return;
}
int fibw_count(viod) {
int rv = 0;
IFBW *ptr = ifbw;
while (ptr) {
rv++;
ptr = ptr->next;
}
return rv;
}
//***********************************
// shm file handling
//***********************************
void shm_create_firejail_dir(void) {
struct stat s;
if (stat("/dev/shm/firejail", &s) == -1) {
/* coverity[toctou] */
if (mkdir("/dev/shm/firejail", 0777) == -1)
errExit("mkdir");
if (chown("/dev/shm/firejail", 0, 0) == -1)
errExit("chown");
}
else { // check /dev/shm/firejail directory belongs to root end exit if doesn't!
if (s.st_uid != 0 || s.st_gid != 0) {
fprintf(stderr, "Error: non-root %s directory, exiting...\n", "/dev/shm/firejail");
exit(1);
}
}
}
static void shm_create_bandwidth_file(pid_t pid) {
char *fname;
if (asprintf(&fname, "/dev/shm/firejail/%d-bandwidth", (int) pid) == -1)
errExit("asprintf");
// if the file already exists, do nothing
struct stat s;
if (stat(fname, &s) == 0) {
free(fname);
return;
}
// create an empty file and set mod and ownership
/* coverity[toctou] */
FILE *fp = fopen(fname, "w");
if (fp) {
fclose(fp);
/* coverity[toctou] */
if (chmod(fname, 0644) == -1)
errExit("chmod");
/* coverity[toctou] */
if (chown(fname, 0, 0) == -1)
errExit("chown");
}
else {
fprintf(stderr, "Error: cannot create bandwidth file in /dev/shm/firejail directory\n");
exit(1);
}
free(fname);
}
// delete shm bandwidth file
void bandwidth_shm_del_file(pid_t pid) {
char *fname;
if (asprintf(&fname, "/dev/shm/firejail/%d-bandwidth", (int) pid) == -1)
errExit("asprintf");
unlink(fname);
free(fname);
}
void network_shm_del_file(pid_t pid) {
char *fname;
if (asprintf(&fname, "/dev/shm/firejail/%d-netmap", (int) pid) == -1)
errExit("asprintf");
unlink(fname);
free(fname);
}
void network_shm_set_file(pid_t pid) {
char *fname;
if (asprintf(&fname, "/dev/shm/firejail/%d-netmap", (int) pid) == -1)
errExit("asprintf");
// create an empty file and set mod and ownership
FILE *fp = fopen(fname, "w");
if (fp) {
if (cfg.bridge0.configured)
fprintf(fp, "%s:%s\n", cfg.bridge0.dev, cfg.bridge0.devsandbox);
if (cfg.bridge1.configured)
fprintf(fp, "%s:%s\n", cfg.bridge1.dev, cfg.bridge1.devsandbox);
if (cfg.bridge2.configured)
fprintf(fp, "%s:%s\n", cfg.bridge2.dev, cfg.bridge2.devsandbox);
if (cfg.bridge3.configured)
fprintf(fp, "%s:%s\n", cfg.bridge3.dev, cfg.bridge3.devsandbox);
fclose(fp);
if (chmod(fname, 0644) == -1)
errExit("chmod");
if (chown(fname, 0, 0) == -1)
errExit("chown");
}
else {
fprintf(stderr, "Error: cannot create network map file in /dev/shm/firejail directory\n");
exit(1);
}
free(fname);
}
void shm_read_bandwidth_file(pid_t pid) {
assert(ifbw == NULL);
char *fname;
if (asprintf(&fname, "/dev/shm/firejail/%d-bandwidth", (int) pid) == -1)
errExit("asprintf");
FILE *fp = fopen(fname, "r");
if (fp) {
char buf[1024];
while (fgets(buf, 1024,fp)) {
// remove '\n'
char *ptr = strchr(buf, '\n');
if (ptr)
*ptr = '\0';
if (strlen(buf) == 0)
continue;
// create a new IFBW entry
IFBW *ifbw_new = malloc(sizeof(IFBW));
if (!ifbw_new)
errExit("malloc");
memset(ifbw_new, 0, sizeof(IFBW));
ifbw_new->txt = strdup(buf);
if (!ifbw_new->txt)
errExit("strdup");
// add it to the linked list
ifbw_add(ifbw_new);
}
fclose(fp);
}
}
void shm_write_bandwidth_file(pid_t pid) {
if (ifbw == NULL)
return; // nothing to do
char *fname;
if (asprintf(&fname, "/dev/shm/firejail/%d-bandwidth", (int) pid) == -1)
errExit("asprintf");
FILE *fp = fopen(fname, "w");
if (fp) {
IFBW *ptr = ifbw;
while (ptr) {
fprintf(fp, "%s\n", ptr->txt);
ptr = ptr->next;
}
fclose(fp);
}
else {
fprintf(stderr, "Error: cannot write bandwidht file %s\n", fname);
exit(1);
}
}
//***********************************
// add or remove interfaces
//***********************************
// remove interface from shm file
void bandwidth_shm_remove(pid_t pid, const char *dev) {
// create bandwidth directory & file in case they are not in the filesystem yet
shm_create_firejail_dir();
shm_create_bandwidth_file(pid);
// read bandwidth file
shm_read_bandwidth_file(pid);
// find the element and remove it
IFBW *elem = ifbw_find(dev);
if (elem) {
ifbw_remove(elem);
shm_write_bandwidth_file(pid) ;
}
// remove the file if there are no entries in the list
if (ifbw == NULL) {
bandwidth_shm_del_file(pid);
}
}
// add interface to shm file
void bandwidth_shm_set(pid_t pid, const char *dev, int down, int up) {
// create bandwidth directory & file in case they are not in the filesystem yet
shm_create_firejail_dir();
shm_create_bandwidth_file(pid);
// create the new text entry
char *txt;
if (asprintf(&txt, "%s: RX %dKB/s, TX %dKB/s", dev, down, up) == -1)
errExit("asprintf");
// read bandwidth file
shm_read_bandwidth_file(pid);
// look for an existing entry and replace the text
IFBW *ptr = ifbw_find(dev);
if (ptr) {
assert(ptr->txt);
free(ptr->txt);
ptr->txt = txt;
}
// ... or add a new entry
else {
IFBW *ifbw_new = malloc(sizeof(IFBW));
if (!ifbw_new)
errExit("malloc");
memset(ifbw_new, 0, sizeof(IFBW));
ifbw_new->txt = txt;
// add it to the linked list
ifbw_add(ifbw_new);
}
shm_write_bandwidth_file(pid) ;
}
//***********************************
// command execution
//***********************************
void bandwidth_name(const char *name, const char *command, const char *dev, int down, int up) {
if (!name || strlen(name) == 0) {
fprintf(stderr, "Error: invalid sandbox name\n");
exit(1);
}
pid_t pid;
if (name2pid(name, &pid)) {
fprintf(stderr, "Error: cannot find sandbox %s\n", name);
exit(1);
}
bandwidth_pid(pid, command, dev, down, up);
}
void bandwidth_pid(pid_t pid, const char *command, const char *dev, int down, int up) {
//************************
// verify sandbox
//************************
char *comm = pid_proc_comm(pid);
if (!comm) {
fprintf(stderr, "Error: cannot find sandbox\n");
exit(1);
}
// remove \n and check for firejail sandbox
char *ptr = strchr(comm, '\n');
if (ptr)
*ptr = '\0';
if (strcmp(comm, "firejail") != 0) {
fprintf(stderr, "Error: cannot find sandbox\n");
exit(1);
}
free(comm);
// check network namespace
char *cmd = pid_proc_cmdline(pid);
if (!cmd || strstr(cmd, "--net") == NULL) {
fprintf(stderr, "Error: the sandbox doesn't use a new network namespace\n");
exit(1);
}
free(cmd);
//************************
// join the network namespace
//************************
pid_t child;
if (find_child(pid, &child) == -1) {
fprintf(stderr, "Error: cannot join the network namespace\n");
exit(1);
}
if (join_namespace(child, "net")) {
fprintf(stderr, "Error: cannot join the network namespace\n");
exit(1);
}
// set shm file
if (strcmp(command, "set") == 0)
bandwidth_shm_set(pid, dev, down, up);
else if (strcmp(command, "clear") == 0)
bandwidth_shm_remove(pid, dev);
//************************
// build command
//************************
char *devname = NULL;
if (dev) {
// read shm network map file
char *fname;
if (asprintf(&fname, "/dev/shm/firejail/%d-netmap", (int) pid) == -1)
errExit("asprintf");
FILE *fp = fopen(fname, "r");
if (!fp) {
fprintf(stderr, "Error: cannot read netowk map filel %s\n", fname);
exit(1);
}
char buf[1024];
int len = strlen(dev);
while (fgets(buf, 1024, fp)) {
// remove '\n'
char *ptr = strchr(buf, '\n');
if (ptr)
*ptr = '\0';
if (*buf == '\0')
break;
if (strncmp(buf, dev, len) == 0 && buf[len] == ':') {
devname = strdup(buf + len + 1);
if (!devname)
errExit("strdup");
// check device in namespace
if (if_nametoindex(devname) == 0) {
fprintf(stderr, "Error: cannot find network device %s\n", devname);
exit(1);
}
break;
}
}
free(fname);
fclose(fp);
}
// build fshaper.sh command
cmd = NULL;
if (devname) {
if (strcmp(command, "set") == 0) {
if (asprintf(&cmd, "%s/lib/firejail/fshaper.sh --%s %s %d %d",
PREFIX, command, devname, down, up) == -1)
errExit("asprintf");
}
else {
if (asprintf(&cmd, "%s/lib/firejail/fshaper.sh --%s %s",
PREFIX, command, devname) == -1)
errExit("asprintf");
}
}
else {
if (asprintf(&cmd, "%s/lib/firejail/fshaper.sh --%s", PREFIX, command) == -1)
errExit("asprintf");
}
assert(cmd);
// wipe out environment variables
environ = NULL;
//************************
// build command
//************************
// elevate privileges
if (setreuid(0, 0))
errExit("setreuid");
if (setregid(0, 0))
errExit("setregid");
char *arg[4];
arg[0] = "/bin/bash";
arg[1] = "-c";
arg[2] = cmd;
arg[3] = NULL;
execvp("/bin/bash", arg);
// it will never get here
exit(0);
}

453
src/firejail/caps.c Normal file
View file

@ -0,0 +1,453 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firejail.h"
#include <errno.h>
#include <linux/filter.h>
#include <stddef.h>
#include <linux/capability.h>
#include <linux/audit.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
extern int capget(cap_user_header_t hdrp, cap_user_data_t datap);
extern int capset(cap_user_header_t hdrp, const cap_user_data_t datap);
typedef struct {
char *name;
int nr;
} CapsEntry;
static CapsEntry capslist[] = {
//
// code generated using tools/extract-caps
// updated manually based on kernel 3.18/include/linux/capability.h (added block suspend and audit_read
//
#ifdef CAP_CHOWN
{"chown", CAP_CHOWN },
#endif
#ifdef CAP_DAC_OVERRIDE
{"dac_override", CAP_DAC_OVERRIDE },
#endif
#ifdef CAP_DAC_READ_SEARCH
{"dac_read_search", CAP_DAC_READ_SEARCH },
#endif
#ifdef CAP_FOWNER
{"fowner", CAP_FOWNER },
#endif
#ifdef CAP_FSETID
{"fsetid", CAP_FSETID },
#endif
#ifdef CAP_KILL
{"kill", CAP_KILL },
#endif
#ifdef CAP_SETGID
{"setgid", CAP_SETGID },
#endif
#ifdef CAP_SETUID
{"setuid", CAP_SETUID },
#endif
#ifdef CAP_SETPCAP
{"setpcap", CAP_SETPCAP },
#endif
#ifdef CAP_LINUX_IMMUTABLE
{"linux_immutable", CAP_LINUX_IMMUTABLE },
#endif
#ifdef CAP_NET_BIND_SERVICE
{"net_bind_service", CAP_NET_BIND_SERVICE },
#endif
#ifdef CAP_NET_BROADCAST
{"net_broadcast", CAP_NET_BROADCAST },
#endif
#ifdef CAP_NET_ADMIN
{"net_admin", CAP_NET_ADMIN },
#endif
#ifdef CAP_NET_RAW
{"net_raw", CAP_NET_RAW },
#endif
#ifdef CAP_IPC_LOCK
{"ipc_lock", CAP_IPC_LOCK },
#endif
#ifdef CAP_IPC_OWNER
{"ipc_owner", CAP_IPC_OWNER },
#endif
#ifdef CAP_SYS_MODULE
{"sys_module", CAP_SYS_MODULE },
#endif
#ifdef CAP_SYS_RAWIO
{"sys_rawio", CAP_SYS_RAWIO },
#endif
#ifdef CAP_SYS_CHROOT
{"sys_chroot", CAP_SYS_CHROOT },
#endif
#ifdef CAP_SYS_PTRACE
{"sys_ptrace", CAP_SYS_PTRACE },
#endif
#ifdef CAP_SYS_PACCT
{"sys_pacct", CAP_SYS_PACCT },
#endif
#ifdef CAP_SYS_ADMIN
{"sys_admin", CAP_SYS_ADMIN },
#endif
#ifdef CAP_SYS_BOOT
{"sys_boot", CAP_SYS_BOOT },
#endif
#ifdef CAP_SYS_NICE
{"sys_nice", CAP_SYS_NICE },
#endif
#ifdef CAP_SYS_RESOURCE
{"sys_resource", CAP_SYS_RESOURCE },
#endif
#ifdef CAP_SYS_TIME
{"sys_time", CAP_SYS_TIME },
#endif
#ifdef CAP_SYS_TTY_CONFIG
{"sys_tty_config", CAP_SYS_TTY_CONFIG },
#endif
#ifdef CAP_MKNOD
{"mknod", CAP_MKNOD },
#endif
#ifdef CAP_LEASE
{"lease", CAP_LEASE },
#endif
#ifdef CAP_AUDIT_WRITE
{"audit_write", CAP_AUDIT_WRITE },
#endif
#ifdef CAP_AUDIT_CONTROL
{"audit_control", CAP_AUDIT_CONTROL },
#endif
#ifdef CAP_SETFCAP
{"setfcap", CAP_SETFCAP },
#endif
#ifdef CAP_MAC_OVERRIDE
{"mac_override", CAP_MAC_OVERRIDE },
#endif
#ifdef CAP_MAC_ADMIN
{"mac_admin", CAP_MAC_ADMIN },
#endif
#ifdef CAP_SYSLOG
{"syslog", CAP_SYSLOG },
#endif
#ifdef CAP_WAKE_ALARM
{"wake_alarm", CAP_WAKE_ALARM },
#endif
// not in Debian 7
#ifdef CAP_BLOCK_SUSPEND
{"block_suspend", CAP_BLOCK_SUSPEND },
#else
{"block_suspend", 36 },
#endif
#ifdef CAP_AUDIT_READ
{"audit_read", CAP_AUDIT_READ },
#else
{"audit_read", 37 },
#endif
//
// end of generated code
//
}; // end of capslist
const char *caps_find_nr(int nr) {
int i;
int elems = sizeof(capslist) / sizeof(capslist[0]);
for (i = 0; i < elems; i++) {
if (nr == capslist[i].nr)
return capslist[i].name;
}
return "unknown";
}
// return -1 if error, or syscall number
static int caps_find_name(const char *name) {
int i;
int elems = sizeof(capslist) / sizeof(capslist[0]);
for (i = 0; i < elems; i++) {
if (strcmp(name, capslist[i].name) == 0)
return capslist[i].nr;
}
return -1;
}
// return 1 if error, 0 if OK
int caps_check_list(const char *clist, void (*callback)(int)) {
// don't allow empty lists
if (clist == NULL || *clist == '\0') {
fprintf(stderr, "Error: empty capabilities lists are not allowed\n");
return -1;
}
// work on a copy of the string
char *str = strdup(clist);
if (!str)
errExit("strdup");
char *ptr = str;
char *start = str;
while (*ptr != '\0') {
if (islower(*ptr) || isdigit(*ptr) || *ptr == '_')
;
else if (*ptr == ',') {
*ptr = '\0';
int nr = caps_find_name(start);
if (nr == -1) {
fprintf(stderr, "Error: capability %s not found\n", start);
free(str);
return -1;
}
else if (callback != NULL)
callback(nr);
start = ptr + 1;
}
ptr++;
}
if (*start != '\0') {
int nr = caps_find_name(start);
if (nr == -1) {
fprintf(stderr, "Error: capability %s not found\n", start);
free(str);
return -1;
}
else if (callback != NULL)
callback(nr);
}
free(str);
return 0;
}
void caps_print(void) {
int i;
int elems = sizeof(capslist) / sizeof(capslist[0]);
// check current caps supported by the kernel
int cnt = 0;
unsigned long cap;
for (cap=0; cap <= 63; cap++) {
int code = prctl(PR_CAPBSET_DROP, cap, 0, 0, 0);
if (code == 0)
cnt++;
}
printf("Your kernel supports %d capabilities.\n", cnt);
for (i = 0; i < elems; i++) {
printf("%d\t- %s\n", capslist[i].nr, capslist[i].name);
}
}
// enabled by default
int caps_default_filter(void) {
// drop capabilities
if (prctl(PR_CAPBSET_DROP, CAP_SYS_MODULE, 0, 0, 0) && arg_debug)
fprintf(stderr, "Warning: cannot drop CAP_SYS_MODULE");
else if (arg_debug)
printf("Drop CAP_SYS_MODULE\n");
if (prctl(PR_CAPBSET_DROP, CAP_SYS_RAWIO, 0, 0, 0) && arg_debug)
fprintf(stderr, "Warning: cannot drop CAP_SYS_RAWIO");
else if (arg_debug)
printf("Drop CAP_SYS_RAWIO\n");
if (prctl(PR_CAPBSET_DROP, CAP_SYS_BOOT, 0, 0, 0) && arg_debug)
fprintf(stderr, "Warning: cannot drop CAP_SYS_BOOT");
else if (arg_debug)
printf("Drop CAP_SYS_BOOT\n");
if (prctl(PR_CAPBSET_DROP, CAP_SYS_NICE, 0, 0, 0) && arg_debug)
fprintf(stderr, "Warning: cannot drop CAP_SYS_NICE");
else if (arg_debug)
printf("Drop CAP_SYS_NICE\n");
if (prctl(PR_CAPBSET_DROP, CAP_SYS_TTY_CONFIG, 0, 0, 0) && arg_debug)
fprintf(stderr, "Warning: cannot drop CAP_SYS_TTY_CONFIG");
else if (arg_debug)
printf("Drop CAP_SYS_TTY_CONFIG\n");
if (prctl(PR_CAPBSET_DROP, CAP_SYSLOG, 0, 0, 0) && arg_debug)
fprintf(stderr, "Warning: cannot drop CAP_SYSLOG");
else if (arg_debug)
printf("Drop CAP_SYSLOG\n");
if (prctl(PR_CAPBSET_DROP, CAP_MKNOD, 0, 0, 0) && arg_debug)
fprintf(stderr, "Warning: cannot drop CAP_MKNOD");
else if (arg_debug)
printf("Drop CAP_MKNOD\n");
if (prctl(PR_CAPBSET_DROP, CAP_SYS_ADMIN, 0, 0, 0) && arg_debug)
fprintf(stderr, "Warning: cannot drop CAP_SYS_ADMIN");
else if (arg_debug)
printf("Drop CAP_SYS_ADMIN\n");
return 0;
}
void caps_drop_all(void) {
if (arg_debug)
printf("Droping all capabilities\n");
unsigned long cap;
for (cap=0; cap <= 63; cap++) {
int code = prctl(PR_CAPBSET_DROP, cap, 0, 0, 0);
if (code == -1 && errno != EINVAL)
errExit("PR_CAPBSET_DROP");
}
}
void caps_set(uint64_t caps) {
if (arg_debug)
printf("Set caps filter %llx\n", (unsigned long long) caps);
unsigned long i;
uint64_t mask = 1LLU;
for (i = 0; i < 64; i++, mask <<= 1) {
if ((mask & caps) == 0) {
int code = prctl(PR_CAPBSET_DROP, i, 0, 0, 0);
if (code == -1 && errno != EINVAL)
errExit("PR_CAPBSET_DROP");
}
}
}
static uint64_t filter;
static void caps_set_bit(int nr) {
uint64_t mask = 1LLU << nr;
filter |= mask;
}
static void caps_reset_bit(int nr) {
uint64_t mask = 1LLU << nr;
filter &= ~mask;
}
void caps_drop_list(const char *clist) {
filter = 0;
filter--;
caps_check_list(clist, caps_reset_bit);
caps_set(filter);
}
void caps_keep_list(const char *clist) {
filter = 0;
caps_check_list(clist, caps_set_bit);
caps_set(filter);
}
#define MAXBUF 4098
static uint64_t extract_caps(int pid) {
char *file;
if (asprintf(&file, "/proc/%d/status", pid) == -1) {
errExit("asprintf");
exit(1);
}
FILE *fp = fopen(file, "r");
if (!fp) {
printf("Error: cannot open %s\n", file);
free(file);
exit(1);
}
char buf[MAXBUF];
while (fgets(buf, MAXBUF, fp)) {
if (strncmp(buf, "CapBnd:", 7) == 0) {
char *ptr = buf + 8;
unsigned long long val;
sscanf(ptr, "%llx", &val);
free(file);
fclose(fp);
return val;
}
}
fclose(fp);
free(file);
printf("Error: cannot read caps configuration\n");
exit(1);
}
void caps_print_filter_name(const char *name) {
if (!name || strlen(name) == 0) {
fprintf(stderr, "Error: invalid sandbox name\n");
exit(1);
}
pid_t pid;
if (name2pid(name, &pid)) {
fprintf(stderr, "Error: cannot find sandbox %s\n", name);
exit(1);
}
caps_print_filter(pid);
}
void caps_print_filter(pid_t pid) {
// if the pid is that of a firejail process, use the pid of the first child process
char *comm = pid_proc_comm(pid);
if (comm) {
// remove \n
char *ptr = strchr(comm, '\n');
if (ptr)
*ptr = '\0';
if (strcmp(comm, "firejail") == 0) {
pid_t child;
if (find_child(pid, &child) == 0) {
pid = child;
}
}
free(comm);
}
// check privileges for non-root users
uid_t uid = getuid();
if (uid != 0) {
struct stat s;
char *dir;
if (asprintf(&dir, "/proc/%u/ns", pid) == -1)
errExit("asprintf");
if (stat(dir, &s) < 0)
errExit("stat");
if (s.st_uid != uid) {
printf("Error: permission denied.\n");
exit(1);
}
}
uint64_t caps = extract_caps(pid);
drop_privs(1);
int i;
uint64_t mask;
int elems = sizeof(capslist) / sizeof(capslist[0]);
for (i = 0, mask = 1; i < elems; i++, mask <<= 1) {
printf("%-18.18s - %s\n", capslist[i].name, (mask & caps)? "enabled": "disabled");
}
exit(0);
}

118
src/firejail/cgroup.c Normal file
View file

@ -0,0 +1,118 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firejail.h"
#include <sys/stat.h>
#define MAXBUF 4096
void save_cgroup(void) {
if (cfg.cgroup == NULL)
return;
char *fname;
if (asprintf(&fname, "%s/cgroup", MNT_DIR) == -1)
errExit(fname);
FILE *fp = fopen(fname, "w");
if (fp) {
fprintf(fp, "%s", cfg.cgroup);
fflush(0);
fclose(fp);
if (chown(fname, 0, 0) < 0)
errExit("chown");
}
else {
fprintf(stderr, "Error: cannot save cgroup\n");
free(fname);
exit(1);
}
free(fname);
}
void load_cgroup(const char *fname) {
if (!fname)
return;
FILE *fp = fopen(fname, "r");
if (fp) {
char buf[MAXBUF];
if (fgets(buf, MAXBUF, fp)) {
cfg.cgroup = strdup(buf);
if (!cfg.cgroup)
errExit("strdup");
}
else
goto errout;
fclose(fp);
return;
}
errout:
fprintf(stderr, "Warrning: cannot load control group\n");
if (fp)
fclose(fp);
}
void set_cgroup(const char *path) {
// path starts with /sys/fs/cgroup
if (strncmp(path, "/sys/fs/cgroup", 14) != 0)
goto errout;
// path ends in tasks
char *ptr = strstr(path, "tasks");
if (!ptr)
goto errout;
if (*(ptr + 5) != '\0')
goto errout;
// no .. traversal
ptr = strstr(path, "..");
if (ptr)
goto errout;
// tasks file exists
struct stat s;
if (stat(path, &s) == -1)
goto errout;
// task file belongs to the user running the sandbox
if (s.st_uid != getuid() && s.st_gid != getgid())
goto errout2;
// add the task to cgroup
/* coverity[toctou] */
FILE *fp = fopen(path, "a");
if (!fp)
goto errout;
pid_t pid = getpid();
int rv = fprintf(fp, "%d\n", pid);
(void) rv;
fclose(fp);
return;
errout:
fprintf(stderr, "Error: invalid cgroup\n");
exit(1);
errout2:
fprintf(stderr, "Error: you don't have permissions to use this control group\n");
exit(1);
}

141
src/firejail/cpu.c Normal file
View file

@ -0,0 +1,141 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firejail.h"
#include <sched.h>
// converts a numeric cpu value in the corresponding bit mask
static void set_cpu(const char *str) {
if (strlen(str) == 0)
return;
int val = atoi(str);
if (val < 0 || val >= 32) {
fprintf(stderr, "Error: invalid cpu number. Accepted values are between 0 and 31.\n");
exit(1);
}
uint32_t mask = 1;
int i;
for (i = 0; i < val; i++, mask <<= 1);
cfg.cpus |= mask;
}
void read_cpu_list(const char *str) {
char *tmp = strdup(str);
if (tmp == NULL)
errExit("strdup");
char *ptr = tmp;
while (*ptr != '\0') {
if (*ptr == ',' || isdigit(*ptr))
;
else {
fprintf(stderr, "Error: invalid cpu list\n");
exit(1);
}
ptr++;
}
char *start = tmp;
ptr = tmp;
while (*ptr != '\0') {
if (*ptr == ',') {
*ptr = '\0';
set_cpu(start);
start = ptr + 1;
}
ptr++;
}
set_cpu(start);
free(tmp);
}
void save_cpu(void) {
if (cfg.cpus == 0)
return;
char *fname;
if (asprintf(&fname, "%s/cpu", MNT_DIR) == -1)
errExit("asprintf");
FILE *fp = fopen(fname, "w");
if (fp) {
fprintf(fp, "%x\n", cfg.cpus);
fclose(fp);
if (chown(fname, 0, 0) < 0)
errExit("chown");
}
else {
fprintf(stderr, "Error: cannot save cpu affinity mask\n");
free(fname);
exit(1);
}
free(fname);
}
void load_cpu(const char *fname) {
if (!fname)
return;
FILE *fp = fopen(fname, "r");
if (fp) {
unsigned tmp;
int rv = fscanf(fp, "%x", &tmp);
if (rv)
cfg.cpus = (uint32_t) tmp;
fclose(fp);
}
else
fprintf(stderr, "Warning: cannot load cpu affinity mask\n");
}
void set_cpu_affinity(void) {
// set cpu affinity
cpu_set_t mask;
CPU_ZERO(&mask);
int i;
uint32_t m = 1;
for (i = 0; i < 32; i++, m <<= 1) {
if (cfg.cpus & m)
CPU_SET(i, &mask);
}
if (sched_setaffinity(0, sizeof(mask), &mask) == -1) {
fprintf(stderr, "Warning: cannot set cpu affinity\n");
fprintf(stderr, " ");
perror("sched_setaffinity");
}
// verify cpu affinity
cpu_set_t mask2;
CPU_ZERO(&mask2);
if (sched_getaffinity(0, sizeof(mask2), &mask2) == -1) {
fprintf(stderr, "Warning: cannot verify cpu affinity\n");
fprintf(stderr, " ");
perror("sched_getaffinity");
}
else {
if (CPU_EQUAL(&mask, &mask2))
printf("CPU affinity set\n");
else
printf("CPU affinity not set\n");
}
}

354
src/firejail/firejail.h Normal file
View file

@ -0,0 +1,354 @@
/*
* Copyright (C) 2014, 2015 Firejail Authors
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef FIREJAIL_H
#define FIREJAIL_H
#include "../include/common.h"
#define USELOCK
#define FIREJAIL_DIR "/tmp/firejail"
#define RO_DIR "/tmp/firejail/firejail.ro.dir"
#define RO_FILE "/tmp/firejail/firejail.ro.file"
#define MNT_DIR "/tmp/firejail/mnt"
#define OVERLAY_DIR "/tmp/firejail/overlay"
#define HOME_DIR "/tmp/firejail/mnt/home"
#define MAX_INCLUDE_LEVEL 6
// main.c
typedef struct bridge_t {
// on the host
char *dev; // interface device name: bridge or regular ethernet
uint32_t ip; // interface device IP address
uint32_t mask; // interface device mask
uint8_t mac[6]; // interface mac address
// inside the sandbox
char *devsandbox; // name of the device inside the sandbox
uint32_t ipsandbox; // ip address inside the sandbox
uint8_t macsandbox[6]; // mac address inside the sandbox
uint32_t iprange_start;// iprange arp scan start range
uint32_t iprange_end; // iprange arp scan end range
// flags
uint8_t arg_ip_none; // --ip=none
uint8_t macvlan; // set by --net=eth0 (or eth1, ...); reset by --net=br0 (or br1, ...)
uint8_t configured;
uint8_t scan; // set by --scan
} Bridge;
typedef struct profile_entry_t {
struct profile_entry_t *next;
char *data;
}ProfileEntry;
typedef struct config_t {
// user data
char *username;
char *homedir;
// filesystem
ProfileEntry *profile;
char *chrootdir; // chroot directory
char *home_private; // private home directory
char *home_private_keep; // keep list for private home directory
char *cwd; // current working directory
// networking
char *hostname;
uint32_t defaultgw; // default gateway
Bridge bridge0;
Bridge bridge1;
Bridge bridge2;
Bridge bridge3;
uint32_t dns1; // up to 3 IP addresses for dns servers
uint32_t dns2;
uint32_t dns3;
// rlimits
unsigned rlimit_nofile;
unsigned rlimit_nproc;
unsigned rlimit_fsize;
unsigned rlimit_sigpending;
// cpu affinity and control groups
uint32_t cpus;
char *cgroup;
// command line
char *command_line;
char *command_name;
char *shell;
char **original_argv;
int original_argc;
int original_program_index;
} Config;
extern Config cfg;
static inline int any_bridge_configured(void) {
if (cfg.bridge3.configured || cfg.bridge2.configured || cfg.bridge1.configured || cfg.bridge0.configured)
return 1;
else
return 0;
}
extern int arg_private; // mount private /home and /tmp directory
extern int arg_debug; // print debug messages
extern int arg_nonetwork; // --net=none
extern int arg_command; // -c
extern int arg_overlay; // --overlay
extern int arg_zsh; // use zsh as default shell
extern int arg_csh; // use csh as default shell
extern int arg_seccomp; // enable default seccomp filter
extern char *arg_seccomp_list;// optional seccomp list on top of default filter
extern char *arg_seccomp_list_drop; // seccomp drop list
extern char *arg_seccomp_list_keep; // seccomp keep list
extern int arg_caps_default_filter; // enable default capabilities filter
extern int arg_caps_drop; // drop list
extern int arg_caps_drop_all; // drop all capabilities
extern int arg_caps_keep; // keep list
extern char *arg_caps_list; // optional caps list
extern int arg_trace; // syscall tracing support
extern int arg_rlimit_nofile; // rlimit nofile
extern int arg_rlimit_nproc; // rlimit nproc
extern int arg_rlimit_fsize; // rlimit fsize
extern int arg_rlimit_sigpending;// rlimit sigpending
extern int arg_nox11; // kill the program if x11 unix domain socket is accessed
extern int arg_nodbus; // kill the program if D-Bus is accessed
extern int arg_nogroups; // disable supplementary groups
extern int arg_noroot; // create a new user namespace and disable root user
extern int arg_netfilter; // enable netfilter
extern char *arg_netfilter_file; // netfilter file
extern int arg_doubledash; // double dash
extern int arg_shell_none; // run the program directly without a shell
extern int arg_private_dev; // private dev directory
extern int arg_scan; // arp-scan all interfaces
extern int parent_to_child_fds[2];
extern int child_to_parent_fds[2];
extern pid_t sandbox_pid;
#define MAX_ARGS 128 // maximum number of command arguments (argc)
extern char *fullargv[MAX_ARGS];
extern int fullargc;
// main.c
void check_user_namespace(void);
// sandbox.c
int sandbox(void* sandbox_arg);
// network_main.c
void net_configure_bridge(Bridge *br, char *dev_name);
void net_configure_sandbox_ip(Bridge *br);
void net_configure_veth_pair(Bridge *br, const char *ifname, pid_t child);
void net_check_cfg(void);
void net_dns_print_name(const char *name);
void net_dns_print(pid_t pid);
// network.c
void net_if_up(const char *ifname);
void net_if_ip(const char *ifname, uint32_t ip, uint32_t mask);
int net_get_if_addr(const char *bridge, uint32_t *ip, uint32_t *mask, uint8_t mac[6]);
int net_add_route(uint32_t dest, uint32_t mask, uint32_t gw);
void net_ifprint(void);
void net_bridge_add_interface(const char *bridge, const char *dev);
uint32_t network_get_defaultgw(void);
int net_config_mac(const char *ifname, const unsigned char mac[6]);
int net_get_mac(const char *ifname, unsigned char mac[6]);
// fs.c
// build /tmp/firejail directory
void fs_build_firejail_dir(void);
// build /tmp/firejail/mnt directory
void fs_build_mnt_dir(void);
// blacklist files or directoies by mounting empty files on top of them
void fs_blacklist(const char *homedir);
//void fs_blacklist(char **blacklist, const char *homedir);
// remount a directory read-only
void fs_rdonly(const char *dir);
// mount /proc and /sys directories
void fs_proc_sys_dev_boot(void);
// build a basic read-only filesystem
void fs_basic_fs(void);
// mount overlayfs on top of / directory
void fs_overlayfs(void);
// chroot into an existing directory; mount exiting /dev and update /etc/resolv.conf
void fs_chroot(const char *rootdir);
int fs_check_chroot_dir(const char *rootdir);
// profile.c
// find and read the profile specified by name from dir directory
int profile_find(const char *name, const char *dir);
// read a profile file
void profile_read(const char *fname, const char *skip1, const char *skip2);
// check profile line; if line == 0, this was generated from a command line option
// return 1 if the command is to be added to the linked list of profile commands
// return 0 if the command was already executed inside the function
int profile_check_line(char *ptr, int lineno);
// add a profile entry in cfg.profile list; use str to populate the list
void profile_add(char *str);
// list.c
void list(void);
void tree(void);
void top(void);
void netstats(void);
// usage.c
void usage(void);
// join.c
void join(pid_t pid, const char *homedir, int argc, char **argv, int index);
void join_name(const char *name, const char *homedir, int argc, char **argv, int index);
void shut(pid_t pid);
void shut_name(const char *name);
// restricted_shell.c
extern char *restricted_user;
int restricted_shell(const char *user);
// arp.c
// returns 0 if the address is not in use, -1 otherwise
int arp_check(const char *dev, uint32_t destaddr, uint32_t srcaddr);
// assign an IP address using arp scanning
uint32_t arp_assign(const char *dev, Bridge *br);
// scan interface (--scan option)
void arp_scan(const char *dev, uint32_t srcaddr, uint32_t srcmask);
// veth.c
int net_create_veth(const char *dev, const char *nsdev, unsigned pid);
int net_create_macvlan(const char *dev, const char *parent, unsigned pid);
// util.c
void drop_privs(int nogroups);
void extract_command_name(const char *str);
void logsignal(int s);
void logmsg(const char *msg);
void logargs(int argc, char **argv) ;
void logerr(const char *msg);
int copy_file(const char *srcname, const char *destname);
char *get_link(const char *fname);
int is_dir(const char *fname);
int is_link(const char *fname);
char *line_remove_spaces(const char *buf);
char *split_comma(char *str);
int not_unsigned(const char *str);
int find_child(pid_t parent, pid_t *child);
void check_private_dir(void);
void update_map(char *mapping, char *map_file);
void wait_for_other(int fd);
void notify_other(int fd);
// fs_var.c
void fs_var_log(void); // mounting /var/log
void fs_var_lib(void); // various other fixes for software in /var directory
void fs_var_cache(void); // various other fixes for software in /var/cache directory
void fs_var_run(void);
void fs_var_lock(void);
void fs_var_tmp(void);
void fs_var_utmp(void);
void dbg_test_dir(const char *dir);
// fs_dev.c
void fs_dev_shm(void);
void fs_private_dev(void);
// fs_home.c
// private mode (--private)
void fs_private(void);
// private mode (--private=homedir)
void fs_private_homedir(void);
// private mode (--private.keep=list)
void fs_private_home_list(void);
// check directory linst specified by user (--private.keep option) - exit if it fails
void fs_check_home_list(void);
// check new private home directory (--private= option) - exit if it fails
void fs_check_private_dir(void);
// seccomp.c
int seccomp_filter_drop(void);
int seccomp_filter_keep(void);
void seccomp_set(void);
void seccomp_print_filter_name(const char *name);
void seccomp_print_filter(pid_t pid);
// caps.c
int caps_default_filter(void);
void caps_print(void);
void caps_drop_all(void);
void caps_set(uint64_t caps);
int caps_check_list(const char *clist, void (*callback)(int));
void caps_drop_list(const char *clist);
void caps_keep_list(const char *clist);
void caps_print_filter(pid_t pid);
void caps_print_filter_name(const char *name);
// syscall.c
const char *syscall_find_nr(int nr);
// return -1 if error, 0 if no error
int syscall_check_list(const char *slist, void (*callback)(int));
// print all available syscalls
void syscall_print(void);
// fs_trace.c
void fs_trace_preload(void);
void fs_trace(void);
// fs_hostname.c
void fs_hostname(const char *hostname);
void fs_resolvconf(void);
// rlimit.c
void set_rlimits(void);
// cpu.c
void read_cpu_list(const char *str);
void set_cpu_affinity(void);
void load_cpu(const char *fname);
void save_cpu(void);
// cgroup.c
void save_cgroup(void);
void load_cgroup(const char *fname);
void set_cgroup(const char *path);
// output.c
void check_output(int argc, char **argv);
// netfilter.c
void check_netfilter_file(const char *fname);
void netfilter(const char *fname);
// bandwidth.c
void shm_create_firejail_dir(void);
void bandwidth_shm_del_file(pid_t pid);
void bandwidth_shm_set(pid_t pid, const char *dev, int down, int up);
void bandwidth_name(const char *name, const char *command, const char *dev, int down, int up);
void bandwidth_pid(pid_t pid, const char *command, const char *dev, int down, int up);
void network_shm_del_file(pid_t pid);
void network_shm_set_file(pid_t pid);
#endif

825
src/firejail/fs.c Normal file
View file

@ -0,0 +1,825 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firejail.h"
#include <sys/mount.h>
#include <sys/stat.h>
#include <linux/limits.h>
#include <glob.h>
#include <dirent.h>
#include <fcntl.h>
#include <errno.h>
// build /tmp/firejail directory
void fs_build_firejail_dir(void) {
struct stat s;
if (stat(FIREJAIL_DIR, &s)) {
if (arg_debug)
printf("Creating %s directory\n", FIREJAIL_DIR);
/* coverity[toctou] */
int rv = mkdir(FIREJAIL_DIR, S_IRWXU | S_IRWXG | S_IRWXO);
if (rv == -1)
errExit("mkdir");
if (chown(FIREJAIL_DIR, 0, 0) < 0)
errExit("chown");
if (chmod(FIREJAIL_DIR, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0)
errExit("chmod");
}
else { // check /tmp/firejail directory belongs to root end exit if doesn't!
if (s.st_uid != 0 || s.st_gid != 0) {
fprintf(stderr, "Error: non-root %s directory, exiting...\n", FIREJAIL_DIR);
exit(1);
}
}
}
// build /tmp/firejail/mnt directory
static int tmpfs_mounted = 0;
void fs_build_mnt_dir(void) {
struct stat s;
fs_build_firejail_dir();
// create /tmp/firejail directory
if (stat(MNT_DIR, &s)) {
if (arg_debug)
printf("Creating %s directory\n", MNT_DIR);
/* coverity[toctou] */
int rv = mkdir(MNT_DIR, S_IRWXU | S_IRWXG | S_IRWXO);
if (rv == -1)
errExit("mkdir");
if (chown(MNT_DIR, 0, 0) < 0)
errExit("chown");
if (chmod(MNT_DIR, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0)
errExit("chmod");
}
// ... and mount tmpfs on top of it
if (!tmpfs_mounted) {
// mount tmpfs on top of /tmp/firejail/mnt
if (arg_debug)
printf("Mounting tmpfs on %s directory\n", MNT_DIR);
if (mount("tmpfs", MNT_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0)
errExit("mounting /tmp/firejail/mnt");
tmpfs_mounted = 1;
}
}
// build /tmp/firejail/overlay directory
void fs_build_overlay_dir(void) {
struct stat s;
fs_build_firejail_dir();
// create /tmp/firejail directory
if (stat(OVERLAY_DIR, &s)) {
if (arg_debug)
printf("Creating %s directory\n", MNT_DIR);
/* coverity[toctou] */
int rv = mkdir(OVERLAY_DIR, S_IRWXU | S_IRWXG | S_IRWXO);
if (rv == -1)
errExit("mkdir");
if (chown(OVERLAY_DIR, 0, 0) < 0)
errExit("chown");
if (chmod(OVERLAY_DIR, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0)
errExit("chmod");
}
}
//***********************************************
// process profile file
//***********************************************
typedef enum {
BLACKLIST_FILE,
MOUNT_READONLY,
MOUNT_TMPFS,
OPERATION_MAX
} OPERATION;
static char *create_empty_dir(void) {
struct stat s;
fs_build_firejail_dir();
if (stat(RO_DIR, &s)) {
/* coverity[toctou] */
int rv = mkdir(RO_DIR, S_IRUSR | S_IXUSR);
if (rv == -1)
errExit("mkdir");
if (chown(RO_DIR, 0, 0) < 0)
errExit("chown");
}
return RO_DIR;
}
static char *create_empty_file(void) {
struct stat s;
fs_build_firejail_dir();
if (stat(RO_FILE, &s)) {
/* coverity[toctou] */
FILE *fp = fopen(RO_FILE, "w");
if (!fp)
errExit("fopen");
fclose(fp);
if (chown(RO_FILE, 0, 0) < 0)
errExit("chown");
if (chmod(RO_FILE, S_IRUSR) < 0)
errExit("chown");
}
return RO_FILE;
}
static void disable_file(OPERATION op, const char *fname, const char *emptydir, const char *emptyfile) {
assert(fname);
assert(emptydir);
assert(emptyfile);
assert(op <OPERATION_MAX);
// if the file is a link, follow the link
char *lnk = NULL;
if (is_link(fname)) {
lnk = get_link(fname);
if (lnk)
fname = lnk;
else
fprintf(stderr, "Warning: cannot follow link %s, skipping...\n", fname);
}
// if the file is not present, do nothing
struct stat s;
if (stat(fname, &s) == -1) {
if (lnk)
free(lnk);
return;
}
// modify the file
if (op == BLACKLIST_FILE) {
if (arg_debug)
printf("Disable %s\n", fname);
if (S_ISDIR(s.st_mode)) {
if (mount(emptydir, fname, "none", MS_BIND, "mode=400,gid=0") < 0)
errExit("disable file");
}
else {
if (mount(emptyfile, fname, "none", MS_BIND, "mode=400,gid=0") < 0)
errExit("disable file");
}
}
else if (op == MOUNT_READONLY) {
if (arg_debug)
printf("Mounting read-only %s\n", fname);
fs_rdonly(fname);
}
else if (op == MOUNT_TMPFS) {
if (S_ISDIR(s.st_mode)) {
if (arg_debug)
printf("Mounting tmpfs on %s\n", fname);
// preserve owner and mode for the directory
if (mount("tmpfs", fname, "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME | MS_REC, 0) < 0)
errExit("mounting tmpfs");
/* coverity[toctou] */
if (chown(fname, s.st_uid, s.st_gid) == -1)
errExit("mounting tmpfs chmod");
}
else
printf("Warning: %s is not a directory; cannot mount a tmpfs on top of it.\n", fname);
}
else
assert(0);
if (lnk)
free(lnk);
}
static void globbing(OPERATION op, const char *fname, const char *emptydir, const char *emptyfile) {
assert(fname);
assert(emptydir);
assert(emptyfile);
// filename globbing: expand * macro and continue processing for every single file
if (strchr(fname, '*')) {
glob_t globbuf;
globbuf.gl_offs = 0;
glob(fname, GLOB_DOOFFS, NULL, &globbuf);
int i;
for (i = 0; i < globbuf.gl_pathc; i++) {
assert(globbuf.gl_pathv[i]);
disable_file(op, globbuf.gl_pathv[i], emptydir, emptyfile);
}
}
else
disable_file(op, fname, emptydir, emptyfile);
}
static void expand_path(OPERATION op, const char *path, const char *fname, const char *emptydir, const char *emptyfile) {
assert(path);
assert(fname);
assert(emptydir);
assert(emptyfile);
char newname[strlen(path) + strlen(fname) + 1];
sprintf(newname, "%s%s", path, fname);
globbing(op, newname, emptydir, emptyfile);
}
// blacklist files or directoies by mounting empty files on top of them
void fs_blacklist(const char *homedir) {
ProfileEntry *entry = cfg.profile;
if (!entry)
return;
char *emptydir = create_empty_dir();
char *emptyfile = create_empty_file();
while (entry) {
OPERATION op = OPERATION_MAX;
char *ptr;
// process blacklist command
if (strncmp(entry->data, "bind", 4) == 0) {
char *dname1 = entry->data + 5;
char *dname2 = split_comma(dname1);
if (dname2 == NULL) {
fprintf(stderr, "Error: second directory missing in bind command\n");
entry = entry->next;
continue;
}
struct stat s;
if (stat(dname1, &s) == -1) {
fprintf(stderr, "Error: cannot find directories for bind command\n");
entry = entry->next;
continue;
}
if (stat(dname2, &s) == -1) {
fprintf(stderr, "Error: cannot find directories for bind command\n");
entry = entry->next;
continue;
}
// mount --bind olddir newdir
if (arg_debug)
printf("Mount-bind %s on top of %s\n", dname1, dname2);
// preserve dname2 mode and ownership
if (mount(dname1, dname2, NULL, MS_BIND|MS_REC, NULL) < 0)
errExit("mount bind");
/* coverity[toctou] */
if (chown(dname2, s.st_uid, s.st_gid) == -1)
errExit("mount-bind chown");
/* coverity[toctou] */
if (chmod(dname2, s.st_mode) == -1)
errExit("mount-bind chmod");
entry = entry->next;
continue;
}
// process blacklist command
if (strncmp(entry->data, "blacklist", 9) == 0) {
ptr = entry->data + 10;
op = BLACKLIST_FILE;
}
else if (strncmp(entry->data, "read-only", 9) == 0) {
ptr = entry->data + 10;
op = MOUNT_READONLY;
}
else if (strncmp(entry->data, "tmpfs", 5) == 0) {
ptr = entry->data + 6;
op = MOUNT_TMPFS;
}
else {
fprintf(stderr, "Error: invalid profile line %s\n", entry->data);
entry = entry->next;
continue;
}
// replace home macro in blacklist array
char *new_name = NULL;
if (strncmp(ptr, "${HOME}", 7) == 0) {
if (asprintf(&new_name, "%s%s", homedir, ptr + 7) == -1)
errExit("asprintf");
ptr = new_name;
}
// expand path macro - look for the file in /bin, /usr/bin, /sbin and /usr/sbin directories
if (strncmp(ptr, "${PATH}", 7) == 0) {
expand_path(op, "/bin", ptr + 7, emptydir, emptyfile);
expand_path(op, "/sbin", ptr + 7, emptydir, emptyfile);
expand_path(op, "/usr/bin", ptr + 7, emptydir, emptyfile);
expand_path(op, "/usr/sbin", ptr + 7, emptydir, emptyfile);
}
else
globbing(op, ptr, emptydir, emptyfile);
if (new_name)
free(new_name);
entry = entry->next;
}
}
//***********************************************
// mount namespace
//***********************************************
// remount a directory read-only
void fs_rdonly(const char *dir) {
assert(dir);
// check directory exists
struct stat s;
int rv = stat(dir, &s);
if (rv == 0) {
// mount --bind /bin /bin
if (mount(dir, dir, NULL, MS_BIND|MS_REC, NULL) < 0)
errExit("mount read-only");
// mount --bind -o remount,ro /bin
if (mount(NULL, dir, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY|MS_REC, NULL) < 0)
errExit("mount read-only");
}
}
void fs_rdonly_noexit(const char *dir) {
assert(dir);
// check directory exists
struct stat s;
int rv = stat(dir, &s);
if (rv == 0) {
int merr = 0;
// mount --bind /bin /bin
if (mount(dir, dir, NULL, MS_BIND|MS_REC, NULL) < 0)
merr = 1;
// mount --bind -o remount,ro /bin
if (mount(NULL, dir, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY|MS_REC, NULL) < 0)
merr = 1;
if (merr)
fprintf(stderr, "Warning: cannot mount %s read-only\n", dir);
}
}
// mount /proc and /sys directories
void fs_proc_sys_dev_boot(void) {
struct stat s;
if (arg_debug)
printf("Remounting /proc and /proc/sys filesystems\n");
if (mount("proc", "/proc", "proc", MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_REC, NULL) < 0)
errExit("mounting /proc");
// remount /proc/sys readonly
if (mount("/proc/sys", "/proc/sys", NULL, MS_BIND | MS_REC, NULL) < 0)
errExit("mounting /proc/sys");
if (mount(NULL, "/proc/sys", NULL, MS_BIND | MS_REMOUNT | MS_RDONLY | MS_REC, NULL) < 0)
errExit("mounting /proc/sys");
/* Mount a version of /sys that describes the network namespace */
if (arg_debug)
printf("Remounting /sys directory\n");
if (umount2("/sys", MNT_DETACH) < 0)
fprintf(stderr, "Warning: failed to unmount /sys\n");
if (mount("sysfs", "/sys", "sysfs", MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REC, NULL) < 0)
fprintf(stderr, "Warning: failed to mount /sys\n");
// if (mount("sysfs", "/sys", "sysfs", MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REC, NULL) < 0)
// errExit("mounting /sys");
// mounting firejail kernel module files
if (stat("/proc/firejail-uptime", &s) == 0) {
errno = 0;
FILE *fp = fopen("/proc/firejail", "w");
int cnt = 0;
while (errno == EBUSY && cnt < 10) {
if (!fp) {
int s = random();
s /= 200000;
usleep(s);
fp = fopen("/proc/firejail", "w");
}
else
break;
}
if (!fp) {
fprintf(stderr, "Error: cannot register sandbox with firejail-lkm\n");
exit(1);
}
if (fp) {
// registration
fprintf(fp, "register\n");
fflush(0);
// filtering x11 connect calls
if (arg_nox11) {
fprintf(fp, "no connect unix /tmp/.X11\n");
fflush(0);
printf("X11 access disabled\n");
}
if (arg_nodbus) {
fprintf(fp, "no connect unix /var/run/dbus/system_bus_socket\n");
fflush(0);
fprintf(fp, "no connect unix /tmp/dbus\n");
fflush(0);
printf("D-Bus access disabled\n");
}
fclose(fp);
if (mount("/proc/firejail-uptime", "/proc/uptime", NULL, MS_BIND|MS_REC, NULL) < 0)
fprintf(stderr, "Warning: cannot mount /proc/firejail-uptime\n");
}
}
// Disable SysRq
// a linux box can be shut down easily using the following commands (as root):
// # echo 1 > /proc/sys/kernel/sysrq
// #echo b > /proc/sysrq-trigger
// for more information see https://www.kernel.org/doc/Documentation/sysrq.txt
if (arg_debug)
printf("Disable /proc/sysrq-trigger\n");
fs_rdonly_noexit("/proc/sysrq-trigger");
// disable hotplug and uevent_helper
if (arg_debug)
printf("Disable /proc/sys/kernel/hotplug\n");
fs_rdonly_noexit("/proc/sys/kernel/hotplug");
if (arg_debug)
printf("Disable /sys/kernel/uevent_helper\n");
fs_rdonly_noexit("/sys/kernel/uevent_helper");
// read-only /proc/irq and /proc/bus
if (arg_debug)
printf("Disable /proc/irq\n");
fs_rdonly_noexit("/proc/irq");
if (arg_debug)
printf("Disable /proc/bus\n");
fs_rdonly_noexit("/proc/bus");
// disable /proc/kcore
disable_file(BLACKLIST_FILE, "/proc/kcore", "not used", "/dev/null");
// disable /proc/kallsyms
disable_file(BLACKLIST_FILE, "/proc/kallsyms", "not used", "/dev/null");
// disable /boot
if (stat("/boot", &s) == 0) {
if (arg_debug)
printf("Mounting a new /boot directory\n");
if (mount("tmpfs", "/boot", "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME | MS_REC, "mode=777,gid=0") < 0)
errExit("mounting /boot directory");
}
// disable /dev/port
if (stat("/dev/port", &s) == 0) {
disable_file(BLACKLIST_FILE, "/dev/port", "not used", "/dev/null");
}
}
static void sanitize_home(void) {
// extract current /home directory data
struct dirent *dir;
DIR *d = opendir("/home");
if (d == NULL)
return;
char *emptydir = create_empty_dir();
while ((dir = readdir(d))) {
if(strcmp(dir->d_name, "." ) == 0 || strcmp(dir->d_name, ".." ) == 0)
continue;
if (dir->d_type == DT_DIR ) {
// get properties
struct stat s;
char *name;
if (asprintf(&name, "/home/%s", dir->d_name) == -1)
continue;
if (stat(name, &s) == -1)
continue;
if (S_ISLNK(s.st_mode)) {
free(name);
continue;
}
if (strcmp(name, cfg.homedir) == 0)
continue;
// printf("directory %u %u:%u #%s#\n",
// s.st_mode,
// s.st_uid,
// s.st_gid,
// name);
// disable directory
disable_file(BLACKLIST_FILE, name, emptydir, "not used");
free(name);
}
}
closedir(d);
}
// build a basic read-only filesystem
void fs_basic_fs(void) {
if (arg_debug)
printf("Mounting read-only /bin, /sbin, /lib, /lib64, /usr, /etc, /var\n");
fs_rdonly("/bin");
fs_rdonly("/sbin");
fs_rdonly("/lib");
fs_rdonly("/lib64");
fs_rdonly("/usr");
fs_rdonly("/etc");
fs_rdonly("/var");
// update /var directory in order to support multiple sandboxes running on the same root directory
if (!arg_private_dev)
fs_dev_shm();
fs_var_lock();
fs_var_tmp();
fs_var_log();
fs_var_lib();
fs_var_cache();
fs_var_utmp();
// only in user mode
if (getuid())
sanitize_home();
}
// mount overlayfs on top of / directory
// mounting an overlay and chrooting into it:
//
// Old Ubuntu kernel
// # cd ~
// # mkdir -p overlay/root
// # mkdir -p overlay/diff
// # mount -t overlayfs -o lowerdir=/,upperdir=/root/overlay/diff overlayfs /root/overlay/root
// # chroot /root/overlay/root
// to shutdown, first exit the chroot and then unmount the overlay
// # exit
// # umount /root/overlay/root
//
// Kernels 3.18+
// # cd ~
// # mkdir -p overlay/root
// # mkdir -p overlay/diff
// # mkdir -p overlay/work
// # mount -t overlay -o lowerdir=/,upperdir=/root/overlay/diff,workdir=/root/overlay/work overlay /root/overlay/root
// # cat /etc/mtab | grep overlay
// /root/overlay /root/overlay/root overlay rw,relatime,lowerdir=/,upperdir=/root/overlay/diff,workdir=/root/overlay/work 0 0
// # chroot /root/overlay/root
// to shutdown, first exit the chroot and then unmount the overlay
// # exit
// # umount /root/overlay/root
// to do: fix the code below; also, it might work without /dev; impose seccomp/caps filters when not root
#include <sys/utsname.h>
void fs_overlayfs(void) {
// check kernel version
struct utsname u;
int rv = uname(&u);
if (rv != 0)
errExit("uname");
int major;
int minor;
if (2 != sscanf(u.release, "%d.%d", &major, &minor)) {
fprintf(stderr, "Error: cannot extract Linux kernel version: %s\n", u.version);
exit(1);
}
if (arg_debug)
printf("Linux kernel version %d.%d\n", major, minor);
int oldkernel = 0;
if (major < 3) {
fprintf(stderr, "Error: minimum kernel version required 3.x\n");
exit(1);
}
if (major == 3 && minor < 18)
oldkernel = 1;
// build overlay directories
fs_build_mnt_dir();
char *oroot;
if(asprintf(&oroot, "%s/oroot", MNT_DIR) == -1)
errExit("asprintf");
if (mkdir(oroot, S_IRWXU | S_IRWXG | S_IRWXO))
errExit("mkdir");
if (chown(oroot, 0, 0) < 0)
errExit("chown");
if (chmod(oroot, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0)
errExit("chmod");
char *odiff;
if(asprintf(&odiff, "%s/odiff", MNT_DIR) == -1)
errExit("asprintf");
if (mkdir(odiff, S_IRWXU | S_IRWXG | S_IRWXO))
errExit("mkdir");
if (chown(odiff, 0, 0) < 0)
errExit("chown");
if (chmod(odiff, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0)
errExit("chmod");
char *owork;
if(asprintf(&owork, "%s/owork", MNT_DIR) == -1)
errExit("asprintf");
if (mkdir(owork, S_IRWXU | S_IRWXG | S_IRWXO))
errExit("mkdir");
if (chown(owork, 0, 0) < 0)
errExit("chown");
if (chmod(owork, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0)
errExit("chmod");
// mount overlayfs
if (arg_debug)
printf("Mounting OverlayFS\n");
char *option;
if (oldkernel) { // old Ubuntu/OpenSUSE kernels
if (asprintf(&option, "lowerdir=/,upperdir=%s", odiff) == -1)
errExit("asprintf");
if (mount("overlayfs", oroot, "overlayfs", MS_MGC_VAL, option) < 0)
errExit("mounting overlayfs");
}
else { // kernel 3.18 or newer
if (asprintf(&option, "lowerdir=/,upperdir=%s,workdir=%s", odiff, owork) == -1)
errExit("asprintf");
if (mount("overlay", oroot, "overlay", MS_MGC_VAL, option) < 0)
errExit("mounting overlayfs");
}
// mount-bind dev directory
if (arg_debug)
printf("Mounting /dev\n");
char *dev;
if (asprintf(&dev, "%s/dev", oroot) == -1)
errExit("asprintf");
if (mount("/dev", dev, NULL, MS_BIND|MS_REC, NULL) < 0)
errExit("mounting /dev");
// chroot in the new filesystem
if (chroot(oroot) == -1)
errExit("chroot");
// update /var directory in order to support multiple sandboxes running on the same root directory
if (!arg_private_dev)
fs_dev_shm();
fs_var_lock();
fs_var_tmp();
fs_var_log();
fs_var_lib();
fs_var_cache();
fs_var_utmp();
// only in user mode
if (getuid())
sanitize_home();
// cleanup and exit
free(option);
free(oroot);
free(odiff);
}
#ifdef HAVE_CHROOT
// return 1 if error
int fs_check_chroot_dir(const char *rootdir) {
assert(rootdir);
struct stat s;
char *name;
// check /dev
if (asprintf(&name, "%s/dev", rootdir) == -1)
errExit("asprintf");
if (stat(name, &s) == -1) {
fprintf(stderr, "Error: cannot find /dev in chroot directory\n");
return 1;
}
free(name);
// check /var/tmp
if (asprintf(&name, "%s/var/tmp", rootdir) == -1)
errExit("asprintf");
if (stat(name, &s) == -1) {
fprintf(stderr, "Error: cannot find /var/tmp in chroot directory\n");
return 1;
}
free(name);
// check /proc
if (asprintf(&name, "%s/proc", rootdir) == -1)
errExit("asprintf");
if (stat(name, &s) == -1) {
fprintf(stderr, "Error: cannot find /proc in chroot directory\n");
return 1;
}
free(name);
// check /proc
if (asprintf(&name, "%s/tmp", rootdir) == -1)
errExit("asprintf");
if (stat(name, &s) == -1) {
fprintf(stderr, "Error: cannot find /tmp in chroot directory\n");
return 1;
}
free(name);
// check /bin/bash
if (asprintf(&name, "%s/bin/bash", rootdir) == -1)
errExit("asprintf");
if (stat(name, &s) == -1) {
fprintf(stderr, "Error: cannot find /bin/bash in chroot directory\n");
return 1;
}
free(name);
return 0;
}
// chroot into an existing directory; mount exiting /dev and update /etc/resolv.conf
void fs_chroot(const char *rootdir) {
assert(rootdir);
//***********************************
// mount-bind a /dev in rootdir
//***********************************
// mount /dev
char *newdev;
if (asprintf(&newdev, "%s/dev", rootdir) == -1)
errExit("asprintf");
if (arg_debug)
printf("Mounting /dev on %s\n", newdev);
if (mount("/dev", newdev, NULL, MS_BIND|MS_REC, NULL) < 0)
errExit("mounting /dev");
// some older distros don't have a /run directory
// create one by default
// no exit on error, let the user deal with any problems
char *rundir;
if (asprintf(&rundir, "%s/run", rootdir) == -1)
errExit("asprintf");
if (!is_dir(rundir)) {
int rv = mkdir(rundir, S_IRWXU | S_IRWXG | S_IRWXO);
(void) rv;
rv = chown(rundir, 0, 0);
(void) rv;
}
// copy /etc/resolv.conf in chroot directory
// if resolv.conf in chroot is a symbolic link, this will fail
// no exit on error, let the user deal with the problem
char *fname;
if (asprintf(&fname, "%s/etc/resolv.conf", rootdir) == -1)
errExit("asprintf");
if (arg_debug)
printf("Updating /etc/resolv.conf in %s\n", fname);
if (copy_file("/etc/resolv.conf", fname) == -1)
fprintf(stderr, "Warning: /etc/resolv.conf not initialized\n");
// chroot into the new directory
if (arg_debug)
printf("Chrooting into %s\n", rootdir);
if (chroot(rootdir) < 0)
errExit("chroot");
// update /var directory in order to support multiple sandboxes running on the same root directory
if (!arg_private_dev)
fs_dev_shm();
fs_var_lock();
fs_var_tmp();
fs_var_log();
fs_var_lib();
fs_var_cache();
fs_var_utmp();
// only in user mode
if (getuid())
sanitize_home();
}
#endif

163
src/firejail/fs_dev.c Normal file
View file

@ -0,0 +1,163 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firejail.h"
#include <sys/mount.h>
#include <sys/stat.h>
#include <linux/limits.h>
#include <glob.h>
#include <dirent.h>
#include <fcntl.h>
#include <pwd.h>
#ifndef _BSD_SOURCE
#define _BSD_SOURCE
#endif
#include <sys/types.h>
static void create_char_dev(const char *path, mode_t mode, int major, int minor) {
dev_t dev = makedev(major, minor);
int rv = mknod(path, S_IFCHR | mode, dev);
if (rv == -1)
goto errexit;
if (chmod(path, mode) < 0)
goto errexit;
if (chown(path, 0, 0) < 0)
goto errexit;
return;
errexit:
fprintf(stderr, "Error: cannot create %s device\n", path);
exit(1);
}
static void create_link(const char *oldpath, const char *newpath) {
if (symlink(oldpath, newpath) == -1)
goto errexit;
if (chown(newpath, 0, 0) < 0)
goto errexit;
return;
errexit:
fprintf(stderr, "Error: cannot create %s device\n", newpath);
exit(1);
}
void fs_private_dev(void){
// install a new /dev directory
if (arg_debug)
printf("Mounting tmpfs on /dev\n");
if (mount("tmpfs", "/dev", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=777,gid=0") < 0)
errExit("mounting /dev");
// create /dev/shm
if (arg_debug)
printf("Create /dev/shm directory\n");
int rv = mkdir("/dev/shm", S_IRWXU | S_IRWXG | S_IRWXO);
if (rv == -1)
errExit("mkdir");
if (chown("/dev/shm", 0, 0) < 0)
errExit("chown");
if (chmod("/dev/shm", S_IRWXU | S_IRWXG | S_IRWXO) < 0)
errExit("chmod");
// create devices
create_char_dev("/dev/zero", 0666, 1, 5); // mknod -m 666 /dev/zero c 1 5
create_char_dev("/dev/null", 0666, 1, 3); // mknod -m 666 /dev/null c 1 3
create_char_dev("/dev/full", 0666, 1, 7); // mknod -m 666 /dev/full c 1 7
create_char_dev("/dev/random", 0666, 1, 8); // Mknod -m 666 /dev/random c 1 8
create_char_dev("/dev/urandom", 0666, 1, 9); // mknod -m 666 /dev/urandom c 1 9
create_char_dev("/dev/tty", 0666, 5, 0); // mknod -m 666 /dev/tty c 5 0
#if 0
create_dev("/dev/tty0", "mknod -m 666 /dev/tty0 c 4 0");
create_dev("/dev/console", "mknod -m 622 /dev/console c 5 1");
#endif
// pseudo-terminal
rv = mkdir("/dev/pts", 0755);
if (rv == -1)
errExit("mkdir");
if (chown("/dev/pts", 0, 0) < 0)
errExit("chown");
if (chmod("/dev/pts", 0755) < 0)
errExit("chmod");
create_char_dev("/dev/pts/ptmx", 0666, 5, 2); //"mknod -m 666 /dev/pts/ptmx c 5 2");
create_link("/dev/pts/ptmx", "/dev/ptmx");
// mount -vt devpts -o newinstance -o ptmxmode=0666 devpts //dev/pts
if (mount("devpts", "/dev/pts", "devpts", MS_MGC_VAL, "newinstance,ptmxmode=0666") < 0)
errExit("mounting /dev/pts");
#if 0
// stdin, stdout, stderr
create_link("/proc/self/fd", "/dev/fd");
create_link("/proc/self/fd/0", "/dev/stdin");
create_link("/proc/self/fd/1", "/dev/stdout");
create_link("/proc/self/fd/2", "/dev/stderr");
#endif
}
void fs_dev_shm(void) {
uid_t uid = getuid(); // set a new shm only if we started as root
if (uid)
return;
if (is_dir("/dev/shm")) {
if (arg_debug)
printf("Mounting tmpfs on /dev/shm\n");
if (mount("tmpfs", "/dev/shm", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=777,gid=0") < 0)
errExit("mounting /dev/shm");
}
else {
char *lnk = get_link("/dev/shm");
if (lnk) {
// convert a link such as "../shm" into "/shm"
char *lnk2 = lnk;
int cnt = 0;
while (strncmp(lnk2, "../", 3) == 0) {
cnt++;
lnk2 = lnk2 + 3;
}
if (cnt != 0)
lnk2 = lnk + (cnt - 1) * 3 + 2;
if (!is_dir(lnk2)) {
// create directory
if (mkdir(lnk2, S_IRWXU|S_IRWXG|S_IRWXO))
errExit("mkdir");
if (chown(lnk2, 0, 0))
errExit("chown");
if (chmod(lnk2, S_IRWXU|S_IRWXG|S_IRWXO))
errExit("chmod");
}
if (arg_debug)
printf("Mounting tmpfs on %s on behalf of /dev/shm\n", lnk2);
if (mount("tmpfs", lnk2, "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=777,gid=0") < 0)
errExit("mounting /var/tmp");
free(lnk);
}
else {
fprintf(stderr, "Warning: /dev/shm not mounted\n");
dbg_test_dir("/dev/shm");
}
}
}

494
src/firejail/fs_home.c Normal file
View file

@ -0,0 +1,494 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firejail.h"
#include <sys/mount.h>
#include <linux/limits.h>
#include <glob.h>
#include <dirent.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <grp.h>
static void skel(const char *homedir, uid_t u, gid_t g) {
char *fname;
// zsh
if (arg_zsh) {
// copy skel files
if (asprintf(&fname, "%s/.zshrc", homedir) == -1)
errExit("asprintf");
struct stat s;
// don't copy it if we already have the file
if (stat(fname, &s) == 0)
return;
if (stat("/etc/skel/.zshrc", &s) == 0) {
if (copy_file("/etc/skel/.zshrc", fname) == 0) {
if (chown(fname, u, g) == -1)
errExit("chown");
}
}
else { //
FILE *fp = fopen(fname, "w");
if (fp) {
fprintf(fp, "\n");
fclose(fp);
if (chown(fname, u, g) == -1)
errExit("chown");
if (chmod(fname, S_IRUSR | S_IWUSR) < 0)
errExit("chown");
}
}
free(fname);
}
// csh
else if (arg_csh) {
// copy skel files
if (asprintf(&fname, "%s/.cshrc", homedir) == -1)
errExit("asprintf");
struct stat s;
// don't copy it if we already have the file
if (stat(fname, &s) == 0)
return;
if (stat("/etc/skel/.cshrc", &s) == 0) {
if (copy_file("/etc/skel/.cshrc", fname) == 0) {
if (chown(fname, u, g) == -1)
errExit("chown");
}
}
else { //
/* coverity[toctou] */
FILE *fp = fopen(fname, "w");
if (fp) {
fprintf(fp, "\n");
fclose(fp);
if (chown(fname, u, g) == -1)
errExit("chown");
if (chmod(fname, S_IRUSR | S_IWUSR) < 0)
errExit("chown");
}
}
free(fname);
}
// bash etc.
else {
// copy skel files
if (asprintf(&fname, "%s/.bashrc", homedir) == -1)
errExit("asprintf");
struct stat s;
// don't copy it if we already have the file
if (stat(fname, &s) == 0)
return;
if (stat("/etc/skel/.bashrc", &s) == 0) {
if (copy_file("/etc/skel/.bashrc", fname) == 0) {
/* coverity[toctou] */
if (chown(fname, u, g) == -1)
errExit("chown");
}
}
free(fname);
}
}
static int store_xauthority(void) {
// put a copy of .Xauthority in MNT_DIR
fs_build_mnt_dir();
char *src;
char *dest;
if (asprintf(&src, "%s/.Xauthority", cfg.homedir) == -1)
errExit("asprintf");
if (asprintf(&dest, "%s/.Xauthority", MNT_DIR) == -1)
errExit("asprintf");
struct stat s;
if (stat(src, &s) == 0) {
int rv = copy_file(src, dest);
if (rv) {
fprintf(stderr, "Warning: cannot transfer .Xauthority in private home directory\n");
return 0;
}
return 1; // file copied
}
return 0;
}
static void copy_xauthority(void) {
// put a copy of .Xauthority in MNT_DIR
fs_build_mnt_dir();
char *src;
char *dest;
if (asprintf(&dest, "%s/.Xauthority", cfg.homedir) == -1)
errExit("asprintf");
if (asprintf(&src, "%s/.Xauthority", MNT_DIR) == -1)
errExit("asprintf");
int rv = copy_file(src, dest);
if (rv)
fprintf(stderr, "Warning: cannot transfer .Xauthority in private home directory\n");
// set permissions and ownership
if (chown(dest, getuid(), getgid()) < 0)
errExit("chown");
if (chmod(dest, S_IRUSR | S_IWUSR) < 0)
errExit("chmod");
// delete the temporary file
unlink(src);
}
// private mode (--private=homedir):
// mount homedir on top of /home/user,
// tmpfs on top of /root in nonroot mode,
// tmpfs on top of /tmp in root mode,
// set skel files,
// restore .Xauthority
void fs_private_homedir(void) {
char *homedir = cfg.homedir;
char *private_homedir = cfg.home_private;
assert(homedir);
assert(private_homedir);
int xflag = store_xauthority();
uid_t u = getuid();
gid_t g = getgid();
struct stat s;
if (stat(homedir, &s) == -1) {
fprintf(stderr, "Error: cannot find user home directory\n");
exit(1);
}
// mount bind private_homedir on top of homedir
if (arg_debug)
printf("Mount-bind %s on top of %s\n", private_homedir, homedir);
if (mount(private_homedir, homedir, NULL, MS_BIND|MS_REC, NULL) < 0)
errExit("mount bind");
// preserve mode and ownership
// if (chown(homedir, s.st_uid, s.st_gid) == -1)
// errExit("mount-bind chown");
// if (chmod(homedir, s.st_mode) == -1)
// errExit("mount-bind chmod");
if (u != 0) {
// mask /root
if (arg_debug)
printf("Mounting a new /root directory\n");
if (mount("tmpfs", "/root", "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME | MS_REC, "mode=700,gid=0") < 0)
errExit("mounting home directory");
}
else {
// mask /home
if (arg_debug)
printf("Mounting a new /home directory\n");
if (mount("tmpfs", "/home", "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0)
errExit("mounting home directory");
// mask /tmp only in root mode; KDE keeps all kind of sockets in /tmp!
if (arg_debug)
printf("Mounting a new /tmp directory\n");
if (mount("tmpfs", "/tmp", "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME | MS_REC, "mode=777,gid=0") < 0)
errExit("mounting tmp directory");
}
skel(homedir, u, g);
if (xflag)
copy_xauthority();
}
// private mode (--private):
// mount tmpfs over /home/user,
// tmpfs on top of /root in nonroot mode,
// tmpfs on top of /tmp in root mode
// set skel files,
// restore .Xauthority
void fs_private(void) {
char *homedir = cfg.homedir;
assert(homedir);
uid_t u = getuid();
gid_t g = getgid();
int xflag = store_xauthority();
// mask /home
if (arg_debug)
printf("Mounting a new /home directory\n");
if (mount("tmpfs", "/home", "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0)
errExit("mounting home directory");
// mask /root
if (arg_debug)
printf("Mounting a new /root directory\n");
if (mount("tmpfs", "/root", "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME | MS_REC, "mode=700,gid=0") < 0)
errExit("mounting home directory");
if (u != 0) {
// create /home/user
if (arg_debug)
printf("Create a new user directory\n");
int rv = mkdir(homedir, S_IRWXU);
if (rv == -1)
errExit("mkdir");
if (chown(homedir, u, g) < 0)
errExit("chown");
}
else {
// mask tmp only in root mode; KDE keeps all kind of sockets in /tmp!
if (arg_debug)
printf("Mounting a new /tmp directory\n");
if (mount("tmpfs", "/tmp", "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME | MS_REC, "mode=777,gid=0") < 0)
errExit("mounting tmp directory");
}
skel(homedir, u, g);
if (xflag)
copy_xauthority();
}
static void check_dir_or_file(const char *name) {
assert(name);
struct stat s;
char *fname;
if (asprintf(&fname, "%s/%s", cfg.homedir, name) == -1)
errExit("asprintf");
if (arg_debug)
printf("***************Checking %s\n", fname);
if (stat(fname, &s) == -1) {
fprintf(stderr, "Error: file %s not found.\n", fname);
exit(1);
}
// check uid
uid_t uid = getuid();
gid_t gid = getgid();
if (s.st_uid != uid || s.st_gid != gid) {
fprintf(stderr, "Error: only files or directories created by the current user are allowed.\n");
exit(1);
}
// dir or regular file
if (S_ISDIR(s.st_mode) || S_ISREG(s.st_mode)) {
free(fname);
return;
}
if (!is_link(fname)) {
free(fname);
return;
}
fprintf(stderr, "Error: invalid file type, %s.\n", fname);
exit(1);
}
// check directory linst specified by user (--private.keep option) - exit if it fails
void fs_check_home_list(void) {
if (strstr(cfg.home_private_keep, "..")) {
fprintf(stderr, "Error: invalid private.keep list\n");
exit(1);
}
char *dlist = strdup(cfg.home_private_keep);
if (!dlist)
errExit("strdup");
char *ptr = strtok(dlist, ",");
check_dir_or_file(ptr);
while ((ptr = strtok(NULL, ",")) != NULL)
check_dir_or_file(ptr);
free(dlist);
}
// check new private home directory (--private= option) - exit if it fails
void fs_check_private_dir(void) {
// if the directory starts with ~, expand the home directory
if (*cfg.home_private == '~') {
char *tmp;
if (asprintf(&tmp, "%s%s", cfg.homedir, cfg.home_private + 1) == -1)
errExit("asprintf");
cfg.home_private = tmp;
}
if (!is_dir(cfg.home_private) || is_link(cfg.home_private) || strstr(cfg.home_private, "..")) {
fprintf(stderr, "Error: invalid private directory\n");
exit(1);
}
// check home directory and chroot home directory have the same owner
struct stat s2;
int rv = stat(cfg.home_private, &s2);
if (rv < 0) {
fprintf(stderr, "Error: cannot find %s directory\n", cfg.home_private);
exit(1);
}
struct stat s1;
rv = stat(cfg.homedir, &s1);
if (rv < 0) {
fprintf(stderr, "Error: cannot find %s directory, full path name required\n", cfg.homedir);
exit(1);
}
if (s1.st_uid != s2.st_uid) {
printf("Error: the two home directories must have the same owner\n");
exit(1);
}
}
#if 0
static int mkpath(char* file_path, mode_t mode) {
assert(file_path && *file_path);
char* p;
for (p=strchr(file_path+1, '/'); p; p=strchr(p+1, '/')) {
*p='\0';
if (mkdir(file_path, mode)==-1) {
if (errno!=EEXIST) { *p='/'; return -1; }
}
*p='/';
}
return 0;
}
#endif
static void duplicate(char *fname) {
char *cmd;
// copy the file
if (asprintf(&cmd, "cp -a --parents %s/%s %s", cfg.homedir, fname, HOME_DIR) == -1)
errExit("asprintf");
if (arg_debug)
printf("%s\n", cmd);
if (system(cmd))
errExit("system cp -a --parents");
free(cmd);
}
// private mode (--private.keep=list):
// mount homedir on top of /home/user,
// tmpfs on top of /root in nonroot mode,
// tmpfs on top of /tmp in root mode,
// set skel files,
// restore .Xauthority
void fs_private_home_list(void) {
char *homedir = cfg.homedir;
char *private_list = cfg.home_private_keep;
assert(homedir);
assert(private_list);
int xflag = store_xauthority();
uid_t u = getuid();
gid_t g = getgid();
struct stat s;
if (stat(homedir, &s) == -1) {
fprintf(stderr, "Error: cannot find user home directory\n");
exit(1);
}
// create /tmp/firejail/mnt/home directory
fs_build_mnt_dir();
int rv = mkdir(HOME_DIR, S_IRWXU | S_IRWXG | S_IRWXO);
if (rv == -1)
errExit("mkdir");
if (chown(HOME_DIR, u, g) < 0)
errExit("chown");
if (chmod(HOME_DIR, 0755) < 0)
errExit("chmod");
// copy the list of files in the new home directory
// using a new child process without root privileges
pid_t child = fork();
if (child < 0)
errExit("fork");
if (child == 0) {
if (arg_debug)
printf("Copying files in the new home:\n");
// drop privileges
if (setgroups(0, NULL) < 0)
errExit("setgroups");
if (setgid(getgid()) < 0)
errExit("setgid/getgid");
if (setuid(getuid()) < 0)
errExit("setuid/getuid");
// copy the list of files in the new home directory
char *dlist = strdup(cfg.home_private_keep);
if (!dlist)
errExit("strdup");
char *ptr = strtok(dlist, ",");
duplicate(ptr);
while ((ptr = strtok(NULL, ",")) != NULL)
duplicate(ptr);
free(dlist);
exit(0);
}
// wait for the child to finish
waitpid(child, NULL, 0);
// mount bind private_homedir on top of homedir
char *newhome;
if (asprintf(&newhome, "%s%s", HOME_DIR, cfg.homedir) == -1)
errExit("asprintf");
if (arg_debug)
printf("Mount-bind %s on top of %s\n", newhome, homedir);
if (mount(newhome, homedir, NULL, MS_BIND|MS_REC, NULL) < 0)
errExit("mount bind");
// preserve mode and ownership
// if (chown(homedir, s.st_uid, s.st_gid) == -1)
// errExit("mount-bind chown");
// if (chmod(homedir, s.st_mode) == -1)
// errExit("mount-bind chmod");
if (u != 0) {
// mask /root
if (arg_debug)
printf("Mounting a new /root directory\n");
if (mount("tmpfs", "/root", "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME | MS_REC, "mode=700,gid=0") < 0)
errExit("mounting home directory");
}
else {
// mask /home
if (arg_debug)
printf("Mounting a new /home directory\n");
if (mount("tmpfs", "/home", "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0)
errExit("mounting home directory");
// mask /tmp only in root mode; KDE keeps all kind of sockets in /tmp!
if (arg_debug)
printf("Mounting a new /tmp directory\n");
if (mount("tmpfs", "/tmp", "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME | MS_REC, "mode=777,gid=0") < 0)
errExit("mounting tmp directory");
}
skel(homedir, u, g);
if (xflag)
copy_xauthority();
}

157
src/firejail/fs_hostname.c Normal file
View file

@ -0,0 +1,157 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firejail.h"
#include <sys/mount.h>
#include <sys/stat.h>
#include <linux/limits.h>
#include <glob.h>
#include <dirent.h>
#include <fcntl.h>
void fs_hostname(const char *hostname) {
struct stat s;
fs_build_mnt_dir();
// create a new /etc/hostname
if (stat("/etc/hostname", &s) == 0) {
if (arg_debug)
printf("Creating a new /etc/hostname file\n");
char *fhost;
if (asprintf(&fhost, "%s/hostname", MNT_DIR) == -1)
errExit("asprintf");
FILE *fp = fopen(fhost, "w");
if (!fp) {
fprintf(stderr, "Error: cannot create %s\n", fhost);
free(fhost);
exit(1);
}
fprintf(fp, "%s\n", hostname);
fclose(fp);
// mode and owner
if (chown(fhost, 0, 0) < 0)
errExit("chown");
if (chmod(fhost, S_IRUSR | S_IWRITE | S_IRGRP | S_IROTH ) < 0)
errExit("chmod");
// bind-mount the file on top of /etc/hostname
if (mount(fhost, "/etc/hostname", NULL, MS_BIND|MS_REC, NULL) < 0)
errExit("mount bind /etc/hostname");
free(fhost);
}
// create a new /etc/hosts
if (stat("/etc/hosts", &s) == 0) {
if (arg_debug)
printf("Creating a new /etc/hosts file\n");
char *fhost;
if (asprintf(&fhost, "%s/hosts", MNT_DIR) == -1)
errExit("asprintf");
// copy /etc/host into our new file, and modify it on the fly
/* coverity[toctou] */
FILE *fp1 = fopen("/etc/hosts", "r");
if (!fp1) {
fprintf(stderr, "Error: cannot open /etc/hosts\n");
free(fhost);
exit(1);
}
FILE *fp2 = fopen(fhost, "w");
if (!fp2) {
fprintf(stderr, "Error: cannot create %s\n", fhost);
free(fhost);
exit(1);
}
char buf[4096];
while (fgets(buf, sizeof(buf), fp1)) {
// remove '\n'
char *ptr = strchr(buf, '\n');
if (ptr)
*ptr = '\0';
// copy line
if (strstr(buf, "127.0.0.1"))
fprintf(fp2, "%s %s\n", buf, hostname);
else
fprintf(fp2, "%s\n", buf);
}
fclose(fp1);
fclose(fp2);
// mode and owner
if (chown(fhost, 0, 0) < 0)
errExit("chown");
if (chmod(fhost, S_IRUSR | S_IWRITE | S_IRGRP | S_IROTH ) < 0)
errExit("chmod");
// bind-mount the file on top of /etc/hostname
if (mount(fhost, "/etc/hosts", NULL, MS_BIND|MS_REC, NULL) < 0)
errExit("mount bind /etc/hosts");
free(fhost);
}
}
void fs_resolvconf(void) {
if (cfg.dns1 == 0)
return;
struct stat s;
fs_build_mnt_dir();
// create a new /etc/hostname
if (stat("/etc/resolv.conf", &s) == 0) {
if (arg_debug)
printf("Creating a new /etc/resolv.conf file\n");
char *fname;
if (asprintf(&fname, "%s/resolv.conf", MNT_DIR) == -1)
errExit("asprintf");
FILE *fp = fopen(fname, "w");
if (!fp) {
fprintf(stderr, "Error: cannot create %s\n", fname);
free(fname);
exit(1);
}
if (cfg.dns1)
fprintf(fp, "nameserver %d.%d.%d.%d\n", PRINT_IP(cfg.dns1));
if (cfg.dns2)
fprintf(fp, "nameserver %d.%d.%d.%d\n", PRINT_IP(cfg.dns2));
if (cfg.dns3)
fprintf(fp, "nameserver %d.%d.%d.%d\n", PRINT_IP(cfg.dns3));
fclose(fp);
// mode and owner
if (chown(fname, 0, 0) < 0)
errExit("chown");
if (chmod(fname, S_IRUSR | S_IWRITE | S_IRGRP | S_IROTH ) < 0)
errExit("chmod");
// bind-mount the file on top of /etc/hostname
if (mount(fname, "/etc/resolv.conf", NULL, MS_BIND|MS_REC, NULL) < 0)
errExit("mount bind /etc/resolv.conf");
free(fname);
}
else {
fprintf(stderr, "Error: cannot set DNS servers, /etc/resolv.conf file is missing\n");
exit(1);
}
}

76
src/firejail/fs_trace.c Normal file
View file

@ -0,0 +1,76 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firejail.h"
#include <sys/mount.h>
#include <sys/stat.h>
#include <linux/limits.h>
#include <glob.h>
#include <dirent.h>
#include <fcntl.h>
#include <pwd.h>
void fs_trace_preload(void) {
struct stat s;
// create an empty /etc/ld.so.preload
if (stat("/etc/ld.so.preload", &s)) {
if (arg_debug)
printf("Creating an empty /etc/ld.so.preload file\n");
/* coverity[toctou] */
FILE *fp = fopen("/etc/ld.so.preload", "w");
if (!fp)
errExit("fopen");
fclose(fp);
if (chown("/etc/ld.so.preload", 0, 0) < 0)
errExit("chown");
if (chmod("/etc/ld.so.preload", S_IRUSR | S_IWRITE | S_IRGRP | S_IROTH ) < 0)
errExit("chmod");
}
}
void fs_trace(void) {
// create /tmp/firejail/mnt directory
fs_build_mnt_dir();
// create the new ld.so.preload file and mount-bind it
if (arg_debug)
printf("Create the new ld.so.preload file\n");
char *preload;
if (asprintf(&preload, "%s/ld.so.preload", MNT_DIR) == -1)
errExit("asprintf");
FILE *fp = fopen(preload, "w");
if (!fp)
errExit("fopen");
fprintf(fp, "%s/lib/firejail/libtrace.so\n", PREFIX);
fclose(fp);
if (chown(preload, 0, 0) < 0)
errExit("chown");
if (chmod(preload, S_IRUSR | S_IWRITE | S_IRGRP | S_IROTH ) < 0)
errExit("chmod");
// mount the new preload file
if (arg_debug)
printf("Mount the new ld.so.preload file\n");
if (mount(preload, "/etc/ld.so.preload", NULL, MS_BIND|MS_REC, NULL) < 0)
errExit("mount bind ls.so.preload");
}

388
src/firejail/fs_var.c Normal file
View file

@ -0,0 +1,388 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firejail.h"
#include <sys/mount.h>
#include <sys/stat.h>
#include <linux/limits.h>
#include <glob.h>
#include <dirent.h>
#include <fcntl.h>
#include <pwd.h>
#include <utmp.h>
#include <time.h>
typedef struct dirdata_t{
struct dirdata_t *next;
char *name;
mode_t st_mode;
uid_t st_uid;
gid_t st_gid;
} DirData;
static DirData *dirlist = NULL;
static void release_all(void) {
DirData *ptr = dirlist;
while (ptr) {
DirData *next = ptr->next;
free(ptr->name);
free(ptr);
ptr = next;
}
dirlist = NULL;
}
static void build_list(const char *srcdir) {
// extract current /var/log directory data
struct dirent *dir;
DIR *d = opendir(srcdir);
if (d == NULL)
return;
while ((dir = readdir(d))) {
if(strcmp(dir->d_name, "." ) == 0 || strcmp(dir->d_name, ".." ) == 0)
continue;
if (dir->d_type == DT_DIR ) {
// get properties
struct stat s;
char *name;
if (asprintf(&name, "%s/%s", srcdir, dir->d_name) == -1)
continue;
if (stat(name, &s) == -1)
continue;
if (S_ISLNK(s.st_mode)) {
free(name);
continue;
}
// printf("directory %u %u:%u %s\n",
// s.st_mode,
// s.st_uid,
// s.st_gid,
// dir->d_name);
DirData *ptr = malloc(sizeof(DirData));
if (ptr == NULL)
errExit("malloc");
memset(ptr, 0, sizeof(DirData));
ptr->name = name;
ptr->st_mode = s.st_mode;
ptr->st_uid = s.st_uid;
ptr->st_gid = s.st_gid;
ptr->next = dirlist;
dirlist = ptr;
}
}
closedir(d);
}
static void build_dirs(void) {
// create directories under /var/log
DirData *ptr = dirlist;
while (ptr) {
if (mkdir(ptr->name, ptr->st_mode))
errExit("mkdir");
if (chown(ptr->name, ptr->st_uid, ptr->st_gid))
errExit("chown");
ptr = ptr->next;
}
}
void fs_var_log(void) {
build_list("/var/log");
// create /var/log if it does't exit
if (is_dir("/var/log")) {
// extract group id for /var/log/wtmp
struct stat s;
gid_t wtmp_group = 0;
if (stat("/var/log/wtmp", &s) == 0)
wtmp_group = s.st_gid;
// mount a tmpfs on top of /var/log
if (arg_debug)
printf("Mounting tmpfs on /var/log\n");
if (mount("tmpfs", "/var/log", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0)
errExit("mounting /var/log");
build_dirs();
release_all();
// create an empty /var/log/wtmp file
/* coverity[toctou] */
FILE *fp = fopen("/var/log/wtmp", "w");
if (fp)
fclose(fp);
if (chown("/var/log/wtmp", 0, wtmp_group) < 0)
errExit("chown");
if (chmod("/var/log/wtmp", S_IRUSR | S_IWRITE | S_IRGRP | S_IWGRP | S_IROTH ) < 0)
errExit("chmod");
// create an empty /var/log/btmp file
fp = fopen("/var/log/btmp", "w");
if (fp)
fclose(fp);
if (chown("/var/log/btmp", 0, wtmp_group) < 0)
errExit("chown");
if (chmod("/var/log/btmp", S_IRUSR | S_IWRITE | S_IRGRP | S_IWGRP) < 0)
errExit("chmod");
}
else
fprintf(stderr, "Warning: cannot mount tmpfs in top of /var/log\n");
}
void fs_var_lib(void) {
struct stat s;
// ISC DHCP multiserver
if (stat("/var/lib/dhcp", &s) == 0) {
if (arg_debug)
printf("Mounting tmpfs on /var/lib/dhcp\n");
if (mount("tmpfs", "/var/lib/dhcp", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0)
errExit("mounting /var/lib/dhcp");
// isc dhcp server requires a /var/lib/dhcp/dhcpd.leases file
FILE *fp = fopen("/var/lib/dhcp/dhcpd.leases", "w");
if (fp) {
fprintf(fp, "\n");
fclose(fp);
if (chown("/var/lib/dhcp/dhcpd.leases", 0, 0) == -1)
errExit("chown");
if (chmod("/var/lib/dhcp/dhcpd.leases", S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH))
errExit("chmod");
}
}
// nginx multiserver
if (stat("/var/lib/nginx", &s) == 0) {
if (arg_debug)
printf("Mounting tmpfs on /var/lib/nginx\n");
if (mount("tmpfs", "/var/lib/nginx", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0)
errExit("mounting /var/lib/nginx");
}
// net-snmp multiserver
if (stat("/var/lib/snmp", &s) == 0) {
if (arg_debug)
printf("Mounting tmpfs on /var/lib/snmp\n");
if (mount("tmpfs", "/var/lib/snmp", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0)
errExit("mounting /var/lib/snmp");
}
// this is where sudo remembers its state
if (stat("/var/lib/sudo", &s) == 0) {
if (arg_debug)
printf("Mounting tmpfs on /var/lib/sudo\n");
if (mount("tmpfs", "/var/lib/sudo", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0)
errExit("mounting /var/lib/sudo");
}
}
void fs_var_cache(void) {
struct stat s;
if (stat("/var/cache/apache2", &s) == 0) {
if (arg_debug)
printf("Mounting tmpfs on /var/cache/apache2\n");
if (mount("tmpfs", "/var/cache/apache2", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0)
errExit("mounting /var/cahce/apache2");
}
if (stat("/var/cache/lighttpd", &s) == 0) {
if (arg_debug)
printf("Mounting tmpfs on /var/cache/lighttpd\n");
if (mount("tmpfs", "/var/cache/lighttpd", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0)
errExit("mounting /var/cache/lighttpd");
struct passwd *p = getpwnam("www-data");
uid_t uid = 0;
gid_t gid = 0;
if (p) {
uid = p->pw_uid;
gid = p->pw_gid;
}
int rv = mkdir("/var/cache/lighttpd/compress", S_IRWXU | S_IRWXG | S_IRWXO);
if (rv == -1)
errExit("mkdir");
if (chown("/var/cache/lighttpd/compress", uid, gid) < 0)
errExit("chown");
rv = mkdir("/var/cache/lighttpd/uploads", S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
if (rv == -1)
errExit("mkdir");
if (chown("/var/cache/lighttpd/uploads", uid, gid) < 0)
errExit("chown");
}
}
void dbg_test_dir(const char *dir) {
if (arg_debug) {
if (is_dir(dir))
printf("%s is a directory\n", dir);
if (is_link(dir)) {
char *lnk = get_link(dir);
if (lnk) {
printf("%s is a symbolic link to %s\n", dir, lnk);
free(lnk);
}
}
}
}
void fs_var_lock(void) {
if (is_dir("/var/lock")) {
if (arg_debug)
printf("Mounting tmpfs on /var/lock\n");
if (mount("tmpfs", "/var/lock", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=777,gid=0") < 0)
errExit("mounting /lock");
}
else {
char *lnk = get_link("/var/lock");
if (lnk) {
// convert a link such as "../shm" into "/shm"
char *lnk2 = lnk;
int cnt = 0;
while (strncmp(lnk2, "../", 3) == 0) {
cnt++;
lnk2 = lnk2 + 3;
}
if (cnt != 0)
lnk2 = lnk + (cnt - 1) * 3 + 2;
if (!is_dir(lnk2)) {
// create directory
if (mkdir(lnk2, S_IRWXU|S_IRWXG|S_IRWXO))
errExit("mkdir");
if (chown(lnk2, 0, 0))
errExit("chown");
if (chmod(lnk2, S_IRWXU|S_IRWXG|S_IRWXO))
errExit("chmod");
}
if (arg_debug)
printf("Mounting tmpfs on %s on behalf of /var/lock\n", lnk2);
if (mount("tmpfs", lnk2, "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=777,gid=0") < 0)
errExit("mounting /var/lock");
free(lnk);
}
else {
fprintf(stderr, "Warning: /var/lock not mounted\n");
dbg_test_dir("/var/lock");
}
}
}
void fs_var_tmp(void) {
if (!is_link("/var/tmp")) {
if (arg_debug)
printf("Mounting tmpfs on /var/tmp\n");
if (mount("tmpfs", "/var/tmp", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=777,gid=0") < 0)
errExit("mounting /var/tmp");
}
else {
fprintf(stderr, "Warning: /var/tmp not mounted\n");
dbg_test_dir("/var/tmp");
}
}
void fs_var_utmp(void) {
struct stat s;
// extract utmp group id
gid_t utmp_group = 0;
if (stat("/var/run/utmp", &s) == 0)
utmp_group = s.st_gid;
else {
fprintf(stderr, "Warning: cannot find /var/run/utmp\n");
return;
}
// create /tmp/firejail/mnt directory
fs_build_mnt_dir();
// create a new utmp file
if (arg_debug)
printf("Create the new utmp file\n");
char *utmp;
if (asprintf(&utmp, "%s/utmp", MNT_DIR) == -1)
errExit("asprintf");
FILE *fp = fopen(utmp, "w");
if (!fp)
errExit("fopen");
// read current utmp
struct utmp *u;
struct utmp u_boot;
setutent();
while ((u = getutent()) != NULL) {
if (u->ut_type == BOOT_TIME) {
memcpy(&u_boot, u, sizeof(u_boot));
u_boot.ut_tv.tv_sec = (unsigned) time(NULL);
}
}
endutent();
// save new utmp file
fwrite(&u_boot, sizeof(u_boot), 1, fp);
fclose(fp);
if (chown(utmp, 0, utmp_group) < 0)
errExit("chown");
if (chmod(utmp, S_IRUSR | S_IWRITE | S_IRGRP | S_IWGRP | S_IROTH ) < 0)
errExit("chmod");
// mount the new utmp file
if (arg_debug)
printf("Mount the new utmp file\n");
if (mount(utmp, "/var/run/utmp", NULL, MS_BIND|MS_REC, NULL) < 0)
errExit("mount bind utmp");
}
#if 0
Testing servers:
brctl addbr br0
ifconfig br0 10.10.20.1/24
apt-get install snmpd
insserv -r snmpd
sudo firejail --net=br0 --ip=10.10.20.10 "/etc/init.d/rsyslog start; /etc/init.d/ssh start; /etc/init.d/snmpd start; sleep inf"
apt-get install apache2
insserv -r apache2
sudo firejail --net=br0 --ip=10.10.20.10 "/etc/init.d/rsyslog start; /etc/init.d/ssh start; /etc/init.d/apache2 start; sleep inf"
apt-get install nginx
insserv -r nginx
sudo firejail --net=br0 --ip=10.10.20.10 "/etc/init.d/rsyslog start; /etc/init.d/ssh start; /etc/init.d/nginx start; sleep inf"
apt-get install lighttpd
insserv -r lighttpd
sudo firejail --net=br0 --ip=10.10.20.10 "/etc/init.d/rsyslog start; /etc/init.d/ssh start; /etc/init.d/lighttpd start; sleep inf"
apt-get install isc-dhcp-server
insserv -r isc-dhcp-server
sudo firejail --net=br0 --ip=10.10.20.10 "/etc/init.d/rsyslog start; /etc/init.d/ssh start; /etc/init.d/isc-dhcp-server start; sleep inf"
#endif

364
src/firejail/join.c Normal file
View file

@ -0,0 +1,364 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firejail.h"
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/prctl.h>
static int apply_caps = 0;
static uint64_t caps = 0;
static int apply_seccomp = 0;
#define BUFLEN 4096
static void extract_command(int argc, char **argv, int index) {
if (index >= argc)
return;
// doubledash followed by positional parameters
if (strcmp(argv[index], "--") == 0) {
arg_doubledash = 1;
index++;
if (index >= argc)
return;
}
// first argv needs to be a valid command
if (arg_doubledash == 0 && *argv[index] == '-') {
fprintf(stderr, "Error: invalid option %s after --join\n", argv[index]);
exit(1);
}
int len = 0;
int i;
// calculate command length
for (i = index; i < argc; i++) {
len += strlen(argv[i]) + 1;
}
assert(len > 0);
// build command
cfg.command_line = malloc(len + 1);
*cfg.command_line = '\0';
for (i = index; i < argc; i++) {
strcat(cfg.command_line, argv[i]);
strcat(cfg.command_line, " ");
}
if (arg_debug)
printf("Extracted command #%s#\n", cfg.command_line);
}
static void extract_nogroups(pid_t pid) {
char *fname;
if (asprintf(&fname, "/proc/%d/root%s/groups", pid, MNT_DIR) == -1)
errExit("asprintf");
struct stat s;
if (stat(fname, &s) == -1)
return;
arg_nogroups = 1;
free(fname);
}
static void extract_cpu(pid_t pid) {
char *fname;
if (asprintf(&fname, "/proc/%d/root%s/cpu", pid, MNT_DIR) == -1)
errExit("asprintf");
struct stat s;
if (stat(fname, &s) == -1)
return;
// there is a cpu file in MNT_DIR; load the information from the file
load_cpu(fname);
free(fname);
}
static void extract_cgroup(pid_t pid) {
char *fname;
if (asprintf(&fname, "/proc/%d/root%s/cgroup", pid, MNT_DIR) == -1)
errExit("asprintf");
struct stat s;
if (stat(fname, &s) == -1)
return;
// there is a cgroup file in MNT_DIR; load the information from the file
load_cgroup(fname);
free(fname);
}
static void extract_caps_seccomp(pid_t pid) {
// open stat file
char *file;
if (asprintf(&file, "/proc/%u/status", pid) == -1) {
perror("asprintf");
exit(1);
}
FILE *fp = fopen(file, "r");
if (!fp) {
free(file);
fprintf(stderr, "Error: cannot open stat file for process %u\n", pid);
exit(1);
}
char buf[BUFLEN];
while (fgets(buf, BUFLEN - 1, fp)) {
if (strncmp(buf, "Seccomp:", 8) == 0) {
char *ptr = buf + 8;
int val;
sscanf(ptr, "%d", &val);
if (val == 2)
apply_seccomp = 1;
break;
}
else if (strncmp(buf, "CapBnd:", 7) == 0) {
char *ptr = buf + 8;
unsigned long long val;
sscanf(ptr, "%llx", &val);
apply_caps = 1;
caps = val;
}
}
fclose(fp);
free(file);
}
void extract_user_namespace(pid_t pid) {
// test user namespaces available in the kernel
struct stat s1;
struct stat s2;
struct stat s3;
if (stat("/proc/self/ns/user", &s1) == 0 &&
stat("/proc/self/uid_map", &s2) == 0 &&
stat("/proc/self/gid_map", &s3) == 0);
else
return;
// read uid map
char *uidmap;
if (asprintf(&uidmap, "/proc/%u/uid_map", pid) == -1)
errExit("asprintf");
FILE *fp = fopen(uidmap, "r");
if (!fp) {
free(uidmap);
return;
}
// check uid map
int u1;
int u2;
if (fscanf(fp, "%d %d", &u1, &u2) == 2) {
if (arg_debug)
printf("User namespace detected: %s, %d, %d\n", uidmap, u1, u2);
if (u1 != 0 || u2 != 0)
arg_noroot = 1;
}
fclose(fp);
free(uidmap);
}
void join_name(const char *name, const char *homedir, int argc, char **argv, int index) {
if (!name || strlen(name) == 0) {
fprintf(stderr, "Error: invalid sandbox name\n");
exit(1);
}
pid_t pid;
if (name2pid(name, &pid)) {
fprintf(stderr, "Error: cannot find sandbox %s\n", name);
exit(1);
}
join(pid, homedir, argc, argv, index);
}
void join(pid_t pid, const char *homedir, int argc, char **argv, int index) {
extract_command(argc, argv, index);
// if the pid is that of a firejail process, use the pid of the first child process
char *comm = pid_proc_comm(pid);
if (comm) {
// remove \n
char *ptr = strchr(comm, '\n');
if (ptr)
*ptr = '\0';
if (strcmp(comm, "firejail") == 0) {
pid_t child;
if (find_child(pid, &child) == 0) {
pid = child;
printf("Switching to pid %u, the first child process inside the sandbox\n", (unsigned) pid);
}
}
free(comm);
}
// check privileges for non-root users
uid_t uid = getuid();
if (uid != 0) {
struct stat s;
char *dir;
if (asprintf(&dir, "/proc/%u/ns", pid) == -1)
errExit("asprintf");
if (stat(dir, &s) < 0)
errExit("stat");
if (s.st_uid != uid) {
fprintf(stderr, "Error: permission is denied to join a sandbox created by a different user.\n");
exit(1);
}
}
// in user mode set caps seccomp, cpu, cgroup, etc
if (getuid() != 0) {
extract_caps_seccomp(pid);
extract_cpu(pid);
extract_cgroup(pid);
extract_nogroups(pid);
extract_user_namespace(pid);
}
// set cgroup
if (cfg.cgroup)
set_cgroup(cfg.cgroup);
// join namespaces
if (join_namespace(pid, "ipc"))
exit(1);
if (join_namespace(pid, "net"))
exit(1);
if (join_namespace(pid, "pid"))
exit(1);
if (join_namespace(pid, "uts"))
exit(1);
if (join_namespace(pid, "mnt"))
exit(1);
pid_t child = fork();
if (child < 0)
errExit("fork");
if (child == 0) {
// chroot into /proc/PID/root directory
char *rootdir;
if (asprintf(&rootdir, "/proc/%d/root", pid) == -1)
errExit("asprintf");
int rv = chroot(rootdir); // this will fail for processes in sandboxes not started with --chroot option
if (rv == 0)
printf("changing root to %s\n", rootdir);
prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0); // kill the child in case the parent died
if (chdir("/") < 0)
errExit("chdir");
if (homedir) {
struct stat s;
if (stat(homedir, &s) == 0) {
/* coverity[toctou] */
if (chdir(homedir) < 0)
errExit("chdir");
}
}
// set cpu affinity
if (cfg.cpus)
set_cpu_affinity();
// set caps filter
if (apply_caps == 1)
caps_set(caps);
#ifdef HAVE_SECCOMP
// set seccomp filter
if (apply_seccomp == 1)
seccomp_set();
#endif
// fix qt 4.8
if (setenv("QT_X11_NO_MITSHM", "1", 1) < 0)
errExit("setenv");
if (setenv("container", "firejail", 1) < 0) // LXC sets container=lxc,
errExit("setenv");
// mount user namespace or drop privileges
if (arg_noroot) {
if (arg_debug)
printf("Joining user namespace\n");
if (join_namespace(1, "user"))
exit(1);
}
else
drop_privs(arg_nogroups);
// set prompt color to green
//export PS1='\[\e[1;32m\][\u@\h \W]\$\[\e[0m\] '
if (setenv("PROMPT_COMMAND", "export PS1=\"\\[\\e[1;32m\\][\\u@\\h \\W]\\$\\[\\e[0m\\] \"", 1) < 0)
errExit("setenv");
// run icmdline trough /bin/bash
if (cfg.command_line == NULL)
// replace the process with a regular bash session
execlp("/bin/bash", "/bin/bash", NULL);
else {
// run the command supplied by the user
int cwd = 0;
if (cfg.cwd) {
if (chdir(cfg.cwd) == 0)
cwd = 1;
}
if (!cwd) {
if (chdir("/") < 0)
errExit("chdir");
if (cfg.homedir) {
struct stat s;
if (stat(cfg.homedir, &s) == 0) {
if (chdir(cfg.homedir) < 0)
errExit("chdir");
}
}
}
char *arg[5];
arg[0] = "/bin/bash";
arg[1] = "-c";
if (arg_debug)
printf("Starting %s\n", cfg.command_line);
if (!arg_doubledash) {
arg[2] = cfg.command_line;
arg[3] = NULL;
}
else {
arg[2] = "--";
arg[3] = cfg.command_line;
arg[4] = NULL;
}
execvp("/bin/bash", arg);
}
// it will never get here!!!
}
// wait for the child to finish
waitpid(child, NULL, 0);
exit(0);
}

65
src/firejail/list.c Normal file
View file

@ -0,0 +1,65 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firejail.h"
void top(void) {
drop_privs(1);
char *arg[4];
arg[0] = "bash";
arg[1] = "-c";
arg[2] = "firemon --top";
arg[3] = NULL;
execvp("/bin/bash", arg);
}
void netstats(void) {
drop_privs(1);
char *arg[4];
arg[0] = "bash";
arg[1] = "-c";
arg[2] = "firemon --netstats";
arg[3] = NULL;
execvp("/bin/bash", arg);
}
void list(void) {
drop_privs(1);
char *arg[4];
arg[0] = "bash";
arg[1] = "-c";
arg[2] = "firemon --list";
arg[3] = NULL;
execvp("/bin/bash", arg);
}
void tree(void) {
drop_privs(1);
char *arg[4];
arg[0] = "bash";
arg[1] = "-c";
arg[2] = "firemon --tree";
arg[3] = NULL;
execvp("/bin/bash", arg);
}

1168
src/firejail/main.c Normal file

File diff suppressed because it is too large Load diff

164
src/firejail/netfilter.c Normal file
View file

@ -0,0 +1,164 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firejail.h"
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
static char *client_filter =
"*filter\n"
":INPUT DROP [0:0]\n"
":FORWARD DROP [0:0]\n"
":OUTPUT ACCEPT [0:0]\n"
"-A INPUT -i lo -j ACCEPT\n"
"# echo replay is handled by -m state RELEATED/ESTABLISHED below\n"
"#-A INPUT -p icmp --icmp-type echo-reply -j ACCEPT\n"
"-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT\n"
"-A INPUT -p icmp --icmp-type destination-unreachable -j ACCEPT\n"
"-A INPUT -p icmp --icmp-type time-exceeded -j ACCEPT\n"
"-A INPUT -p icmp --icmp-type echo-request -j ACCEPT \n"
"COMMIT\n";
void check_netfilter_file(const char *fname) {
if (is_dir(fname) || is_link(fname) || strstr(fname, "..")) {
fprintf(stderr, "Error: invalid network filter file\n");
exit(1);
}
// access call checks as real UID/GID, not as effective UID/GID
if (access(fname, R_OK)) {
fprintf(stderr, "Error: cannot access network filter file\n");
exit(1);
}
}
void netfilter(const char *fname) {
// default filter
char *filter = client_filter;
// custom filter
int allocated = 0;
if (fname) {
// buffer the filter
struct stat s;
if (stat(fname, &s) == -1) {
fprintf(stderr, "Error: cannot find network filter file\n");
exit(1);
}
filter = malloc(s.st_size + 1); // + '\0'
memset(filter, 0, s.st_size + 1);
if (!filter)
errExit("malloc");
/* coverity[toctou] */
FILE *fp = fopen(fname, "r");
if (!fp) {
fprintf(stderr, "Error: cannot open network filter file\n");
exit(1);
}
size_t sz = fread(filter, 1, s.st_size, fp);
if (sz != s.st_size) {
fprintf(stderr, "Error: cannot read network filter file\n");
exit(1);
}
fclose(fp);
allocated = 1;
}
// mount a tempfs on top of /tmp directory
if (mount("tmpfs", "/tmp", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0)
errExit("mounting /tmp");
// create the filter file
FILE *fp = fopen("/tmp/netfilter", "w");
if (!fp) {
fprintf(stderr, "Error: cannot open /tmp/netfilter file\n");
exit(1);
}
fprintf(fp, "%s\n", filter);
fclose(fp);
// find iptables command
struct stat s;
char *iptables = NULL;
char *iptables_restore = NULL;
if (stat("/sbin/iptables", &s) == 0) {
iptables = "/sbin/iptables";
iptables_restore = "/sbin/iptables-restore";
}
else if (stat("/usr/sbin/iptables", &s) == 0) {
iptables = "/usr/sbin/iptables";
iptables_restore = "/usr/sbin/iptables-restore";
}
if (iptables == NULL || iptables_restore == NULL) {
fprintf(stderr, "Error: iptables command not found\n");
goto doexit;
}
// push filter
pid_t child = fork();
if (child < 0)
errExit("fork");
if (child == 0) {
if (arg_debug)
printf("Installing network filter:\n%s\n", filter);
int fd;
if((fd = open("/tmp/netfilter", O_RDONLY)) == -1) {
fprintf(stderr,"Error: cannot open /tmp/netfilter\n");
exit(1);
}
dup2(fd,STDIN_FILENO);
close(fd);
// wipe out environment variables
environ = NULL;
execl(iptables_restore, iptables_restore, NULL);
// it will never get here!!!
}
// wait for the child to finish
waitpid(child, NULL, 0);
// debug
if (arg_debug) {
child = fork();
if (child < 0)
errExit("fork");
if (child == 0) {
environ = NULL;
execl(iptables, iptables, "-vL", NULL);
// it will never get here!!!
}
// wait for the child to finish
waitpid(child, NULL, 0);
}
doexit:
// unmount /tmp
umount("/tmp");
if (allocated)
free(filter);
}

362
src/firejail/network.c Normal file
View file

@ -0,0 +1,362 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firejail.h"
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netdb.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <net/route.h>
#include <linux/if_bridge.h>
// scan interfaces in current namespace and print IP address/mask for each interface
void net_ifprint(void) {
uint32_t ip;
uint32_t mask;
struct ifaddrs *ifaddr, *ifa;
if (getifaddrs(&ifaddr) == -1)
errExit("getifaddrs");
printf("%-17.17s%-19.19s%-17.17s%-17.17s%-6.6s\n",
"Interface", "MAC", "IP", "Mask", "Status");
// walk through the linked list
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == NULL)
continue;
if (ifa->ifa_addr->sa_family == AF_INET) {
struct sockaddr_in *si = (struct sockaddr_in *) ifa->ifa_netmask;
mask = ntohl(si->sin_addr.s_addr);
si = (struct sockaddr_in *) ifa->ifa_addr;
ip = ntohl(si->sin_addr.s_addr);
// interface status
char *status;
if (ifa->ifa_flags & IFF_RUNNING && ifa->ifa_flags & IFF_UP)
status = "UP";
else
status = "DOWN";
// ip address and mask
char ipstr[30];
sprintf(ipstr, "%d.%d.%d.%d", PRINT_IP(ip));
char maskstr[30];
sprintf(maskstr, "%d.%d.%d.%d", PRINT_IP(mask));
// mac address
unsigned char mac[6];
net_get_mac(ifa->ifa_name, mac);
char macstr[30];
if (strcmp(ifa->ifa_name, "lo") == 0)
macstr[0] = '\0';
else
sprintf(macstr, "%02x:%02x:%02x:%02x:%02x:%02x", PRINT_MAC(mac));
// print
printf("%-17.17s%-19.19s%-17.17s%-17.17s%-6.6s\n",
ifa->ifa_name, macstr, ipstr, maskstr, status);
// network scanning
if (!arg_scan) // scanning disabled
continue;
if (strcmp(ifa->ifa_name, "lo") == 0) // no loopbabck scanning
continue;
if (mask2bits(mask) < 16) // not scanning large networks
continue;
if (!ip) // if not configured
continue;
// only if the interface is up and running
if (ifa->ifa_flags & IFF_RUNNING && ifa->ifa_flags & IFF_UP)
arp_scan(ifa->ifa_name, ip, mask);
}
}
freeifaddrs(ifaddr);
}
// return -1 if the interface was not found; if the interface was found retrn 0 and fill in IP address and mask
int net_get_if_addr(const char *bridge, uint32_t *ip, uint32_t *mask, uint8_t mac[6]) {
assert(bridge);
assert(ip);
assert(mask);
int rv = -1;
struct ifaddrs *ifaddr, *ifa;
if (getifaddrs(&ifaddr) == -1)
errExit("getifaddrs");
// walk through the linked list; if the interface is found, extract IP address and mask
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == NULL)
continue;
if (strcmp(ifa->ifa_name, bridge) != 0)
continue;
if (ifa->ifa_addr->sa_family == AF_INET) {
struct sockaddr_in *si = (struct sockaddr_in *) ifa->ifa_netmask;
*mask = ntohl(si->sin_addr.s_addr);
si = (struct sockaddr_in *) ifa->ifa_addr;
*ip = ntohl(si->sin_addr.s_addr);
if (strcmp(ifa->ifa_name, "lo") != 0)
net_get_mac(ifa->ifa_name, mac);
rv = 0;
break;
}
}
freeifaddrs(ifaddr);
return rv;
}
// bring interface up
void net_if_up(const char *ifname) {
if (strlen(ifname) > IFNAMSIZ) {
fprintf(stderr, "Error: invalid network device name %s\n", ifname);
exit(1);
}
int sock = socket(AF_INET,SOCK_DGRAM,0);
if (sock < 0)
errExit("socket");
// get the existing interface flags
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
ifr.ifr_addr.sa_family = AF_INET;
// read the existing flags
if (ioctl(sock, SIOCGIFFLAGS, &ifr ) < 0) {
close(sock);
errExit("ioctl");
}
ifr.ifr_flags |= IFF_UP;
// set the new flags
if (ioctl( sock, SIOCSIFFLAGS, &ifr ) < 0) {
close(sock);
errExit("ioctl");
}
// checking
// read the existing flags
if (ioctl(sock, SIOCGIFFLAGS, &ifr ) < 0) {
close(sock);
errExit("ioctl");
}
// wait not more than 500ms for the interface to come up
int cnt = 0;
while (cnt < 50) {
usleep(10000); // sleep 10ms
// read the existing flags
if (ioctl(sock, SIOCGIFFLAGS, &ifr ) < 0) {
close(sock);
errExit("ioctl");
}
if (ifr.ifr_flags & IFF_RUNNING)
break;
cnt++;
}
close(sock);
}
// configure interface
void net_if_ip(const char *ifname, uint32_t ip, uint32_t mask) {
if (strlen(ifname) > IFNAMSIZ) {
fprintf(stderr, "Error: invalid network device name %s\n", ifname);
exit(1);
}
int sock = socket(AF_INET,SOCK_DGRAM,0);
if (sock < 0)
errExit("socket");
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
ifr.ifr_addr.sa_family = AF_INET;
((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr.s_addr = htonl(ip);
if (ioctl( sock, SIOCSIFADDR, &ifr ) < 0) {
close(sock);
errExit("ioctl");
}
if (ip != 0) {
((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr.s_addr = htonl(mask);
if (ioctl( sock, SIOCSIFNETMASK, &ifr ) < 0) {
close(sock);
errExit("ioctl");
}
}
close(sock);
usleep(10000); // sleep 10ms
}
// add an IP route, return -1 if error, 0 if the route was added
int net_add_route(uint32_t ip, uint32_t mask, uint32_t gw) {
int sock;
struct rtentry route;
struct sockaddr_in *addr;
int err = 0;
// create the socket
if((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
errExit("socket");
memset(&route, 0, sizeof(route));
addr = (struct sockaddr_in*) &route.rt_gateway;
addr->sin_family = AF_INET;
addr->sin_addr.s_addr = htonl(gw);
addr = (struct sockaddr_in*) &route.rt_dst;
addr->sin_family = AF_INET;
addr->sin_addr.s_addr = htonl(ip);
addr = (struct sockaddr_in*) &route.rt_genmask;
addr->sin_family = AF_INET;
addr->sin_addr.s_addr = htonl(mask);
route.rt_flags = RTF_UP | RTF_GATEWAY;
route.rt_metric = 0;
if ((err = ioctl(sock, SIOCADDRT, &route)) != 0) {
close(sock);
return -1;
}
close(sock);
return 0;
}
// add a veth device to a bridge
void net_bridge_add_interface(const char *bridge, const char *dev) {
if (strlen(bridge) > IFNAMSIZ) {
fprintf(stderr, "Error: invalid network device name %s\n", bridge);
exit(1);
}
struct ifreq ifr;
int err;
int ifindex = if_nametoindex(dev);
if (ifindex <= 0)
errExit("if_nametoindex");
int sock;
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
errExit("socket");
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, bridge, IFNAMSIZ);
#ifdef SIOCBRADDIF
ifr.ifr_ifindex = ifindex;
err = ioctl(sock, SIOCBRADDIF, &ifr);
if (err < 0)
#endif
{
unsigned long args[4] = { BRCTL_ADD_IF, ifindex, 0, 0 };
ifr.ifr_data = (char *) args;
err = ioctl(sock, SIOCDEVPRIVATE, &ifr);
}
(void) err;
close(sock);
}
#define BUFSIZE 1024
uint32_t network_get_defaultgw(void) {
FILE *fp = fopen("/proc/self/net/route", "r");
if (!fp)
errExit("fopen");
char buf[BUFSIZE];
uint32_t retval = 0;
while (fgets(buf, BUFSIZE, fp)) {
if (strncmp(buf, "Iface", 5) == 0)
continue;
char *ptr = buf;
while (*ptr != ' ' && *ptr != '\t')
ptr++;
while (*ptr == ' ' || *ptr == '\t')
ptr++;
unsigned dest;
unsigned gw;
int rv = sscanf(ptr, "%x %x", &dest, &gw);
if (rv == 2 && dest == 0) {
retval = ntohl(gw);
break;
}
}
fclose(fp);
return retval;
}
int net_config_mac(const char *ifname, const unsigned char mac[6]) {
struct ifreq ifr;
int sock;
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
errExit("socket");
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
ifr.ifr_hwaddr.sa_family = ARPHRD_ETHER;
memcpy(ifr.ifr_hwaddr.sa_data, mac, 6);
if (ioctl(sock, SIOCSIFHWADDR, &ifr) == -1)
errExit("ioctl");
close(sock);
return 0;
}
int net_get_mac(const char *ifname, unsigned char mac[6]) {
struct ifreq ifr;
int sock;
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
errExit("socket");
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
ifr.ifr_hwaddr.sa_family = ARPHRD_ETHER;
if (ioctl(sock, SIOCGIFHWADDR, &ifr) == -1)
errExit("ioctl");
memcpy(mac, ifr.ifr_hwaddr.sa_data, 6);
close(sock);
return 0;
}

95
src/firejail/network.txt Normal file
View file

@ -0,0 +1,95 @@
struct Bridge {
char *dev; // bridge device name
uint32_t ip; // bridge device IP address
uint32_t mask; // bridge device mask
uint32_t ipsandbox // sandbox interface IP address
}
net_configure_bridge(br, device) {
br->dev = devname;
br->ip = extracted from kernel device - using net_get_if_addr() in network.c
br->mask = extracted from kernel device - using net_get_if_addr() in network.c
check available network range; /31 networks are not supported
}
net_configure_sandbox_ip(br) {
if br->ip_snadbox
check br->ipsandbox inside the bridge network
arp_check(br->ipsandbox) // send an arp req to check if anybody else is using this address
else
br->ipsandbox = arp_assign();
}
net_configure_veth_pair {
create a veth pair
place one interface end in the bridge
place the other end in the namespace of the child process
}
net_bridge_wait_ip {
arp_check br->ipsandbox address to come up
wait for not more than 5 seconds
}
main() {
foreach argv[i] {
if --net
br = next bridge available
net_configure_bridge(br, device name from argv[i]);
else if --ip
br = last bridge configured
br->ipsandbox = ip address extracted from argv[i]
else if --defaultgw
cfg.defaultgw = ip address extracted from argv[i]
}
net_check_cfg(); // check the validity of network configuration so far
if (any bridge configured) {
lock /var/lock/firejail.lock file
for each bridge
net_configure_sandbox_ip(br)
}
clone (new network namespace if any bridge configured or --net=none)
if (any bridge configured) {
for each bridge
net_configure_veth_pair
}
notify child init is done
if (any bridge configured) {
for each bridge
net_bridge_wait_ip
unlock /var/lock/firejail.lock file
}
wait on child
exit
}
******************************************************
* macvlan notes
******************************************************
Configure a macvlan interface
# ip link add virtual0 link eth0 type macvlan mode bridge
(you can configure it with # ifconfig virtual0 192.168.1.52/24 up)
Create a new network namespace and move the interface in the new network namespace
# ip netns add dummy0
# ip link set virtual0 netns dummy0
Join the namespace and configure the interfaces
# ip netns exec dummy0 bash
# ifconfig lo up
# ifconfig virtual0 192.168.1.52/24
Investigate ipvlan interface - added to linux kernel 3.19
https://github.com/torvalds/linux/blob/master/Documentation/networking/ipvlan.txt

268
src/firejail/network_main.c Normal file
View file

@ -0,0 +1,268 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firejail.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <net/if.h>
// configure bridge structure
// - extract ip address and mask from the bridge interface
void net_configure_bridge(Bridge *br, char *dev_name) {
assert(br);
assert(dev_name);
br->dev = dev_name;
// check the bridge device exists
char sysbridge[30 + strlen(br->dev)];
sprintf(sysbridge, "/sys/class/net/%s/bridge", br->dev);
struct stat s;
int rv = stat(sysbridge, &s);
if (rv == 0) {
// this is a bridge device
br->macvlan = 0;
}
else {
// is this a regular Ethernet interface
if (if_nametoindex(br->dev) > 0) {
br->macvlan = 1;
char *newname;
if (asprintf(&newname, "%s-%u", br->devsandbox, getpid()) == -1)
errExit("asprintf");
br->devsandbox = newname;
}
else {
fprintf(stderr, "Error: cannot find network device %s\n", br->dev);
exit(1);
}
}
if (net_get_if_addr(br->dev, &br->ip, &br->mask, br->mac)) {
fprintf(stderr, "Error: interface %s is not configured\n", br->dev);
exit(1);
}
if (arg_debug) {
if (br->macvlan == 0)
printf("Bridge device %s at %d.%d.%d.%d/%d\n",
br->dev, PRINT_IP(br->ip), mask2bits(br->mask));
else
printf("macvlan parent device %s at %d.%d.%d.%d/%d\n",
br->dev, PRINT_IP(br->ip), mask2bits(br->mask));
}
uint32_t range = ~br->mask + 1; // the number of potential addresses
// this software is not supported for /31 networks
if (range < 4) {
fprintf(stderr, "Error: the software is not supported for /31 networks\n");
exit(1);
}
br->configured = 1;
}
void net_configure_sandbox_ip(Bridge *br) {
assert(br);
if (br->configured == 0)
return;
if (br->arg_ip_none)
br->ipsandbox = 0;
else if (br->ipsandbox) {
// check network range
char *rv = in_netrange(br->ipsandbox, br->ip, br->mask);
if (rv) {
fprintf(stderr, "%s", rv);
exit(1);
}
// send an ARP request and check if there is anybody on this IP address
if (arp_check(br->dev, br->ipsandbox, br->ip)) {
fprintf(stderr, "Error: IP address %d.%d.%d.%d is already in use\n", PRINT_IP(br->ipsandbox));
exit(1);
}
}
else
// ip address assigned by arp-scan for a bridge device
br->ipsandbox = arp_assign(br->dev, br); //br->ip, br->mask);
}
// create a veth pair
// - br - bridge device
// - ifname - interface name in sandbox namespace
// - child - child process running the namespace
void net_configure_veth_pair(Bridge *br, const char *ifname, pid_t child) {
assert(br);
if (br->configured == 0)
return;
// create a veth pair
char *dev;
if (asprintf(&dev, "veth%u%s", getpid(), ifname) < 0)
errExit("asprintf");
net_create_veth(dev, ifname, child);
// bring up the interface
net_if_up(dev);
// add interface to the bridge
net_bridge_add_interface(br->dev, dev);
char *msg;
if (asprintf(&msg, "%d.%d.%d.%d address assigned to sandbox", PRINT_IP(br->ipsandbox)) == -1)
errExit("asprintf");
logmsg(msg);
fflush(0);
free(msg);
}
// the default address should be in the range of at least on of the bridge devices
void check_default_gw(uint32_t defaultgw) {
assert(defaultgw);
if (cfg.bridge0.configured) {
char *rv = in_netrange(defaultgw, cfg.bridge0.ip, cfg.bridge0.mask);
if (rv == 0)
return;
}
if (cfg.bridge1.configured) {
char *rv = in_netrange(defaultgw, cfg.bridge1.ip, cfg.bridge1.mask);
if (rv == 0)
return;
}
if (cfg.bridge2.configured) {
char *rv = in_netrange(defaultgw, cfg.bridge2.ip, cfg.bridge2.mask);
if (rv == 0)
return;
}
if (cfg.bridge3.configured) {
char *rv = in_netrange(defaultgw, cfg.bridge3.ip, cfg.bridge3.mask);
if (rv == 0)
return;
}
fprintf(stderr, "Error: default gateway %d.%d.%d.%d is not in the range of any network\n", PRINT_IP(defaultgw));
exit(1);
}
void net_check_cfg(void) {
int net_configured = 0;
if (cfg.bridge0.configured)
net_configured++;
if (cfg.bridge1.configured)
net_configured++;
if (cfg.bridge2.configured)
net_configured++;
if (cfg.bridge3.configured)
net_configured++;
// --defaultgw requires a network
if (cfg.defaultgw && net_configured == 0) {
fprintf(stderr, "Error: option --defaultgw requires at least one network to be configured\n");
exit(1);
}
if (net_configured == 0) // nothing to check
return;
// --net=none
if (arg_nonetwork && net_configured) {
fprintf(stderr, "Error: --net and --net=none are mutually exclusive\n");
exit(1);
}
// check default gateway address or assign one
assert(cfg.bridge0.configured);
if (cfg.defaultgw)
check_default_gw(cfg.defaultgw);
else {
// first network is a regular bridge
if (cfg.bridge0.macvlan == 0)
cfg.defaultgw = cfg.bridge0.ip;
// first network is a mac device
else {
// get the host default gw
uint32_t gw = network_get_defaultgw();
// check the gateway is network range
if (in_netrange(gw, cfg.bridge0.ip, cfg.bridge0.mask))
gw = 0;
cfg.defaultgw = gw;
}
}
}
void net_dns_print_name(const char *name) {
if (!name || strlen(name) == 0) {
fprintf(stderr, "Error: invalid sandbox name\n");
exit(1);
}
pid_t pid;
if (name2pid(name, &pid)) {
fprintf(stderr, "Error: cannot find sandbox %s\n", name);
exit(1);
}
net_dns_print(pid);
}
#define MAXBUF 4096
void net_dns_print(pid_t pid) {
// drop privileges - will not be able to read /etc/resolv.conf for --noroot option
// drop_privs(1);
// if the pid is that of a firejail process, use the pid of the first child process
char *comm = pid_proc_comm(pid);
if (comm) {
// remove \n
char *ptr = strchr(comm, '\n');
if (ptr)
*ptr = '\0';
if (strcmp(comm, "firejail") == 0) {
pid_t child;
if (find_child(pid, &child) == 0) {
pid = child;
}
}
free(comm);
}
char *fname;
if (asprintf(&fname, "/proc/%d/root/etc/resolv.conf", pid) == -1)
errExit("asprintf");
// access /etc/resolv.conf
FILE *fp = fopen(fname, "r");
if (!fp) {
fprintf(stderr, "Error: cannot access /etc/resolv.conf\n");
exit(1);
}
char buf[MAXBUF];
while (fgets(buf, MAXBUF, fp))
printf("%s", buf);
printf("\n");
fclose(fp);
free(fname);
exit(0);
}

84
src/firejail/output.c Normal file
View file

@ -0,0 +1,84 @@
#include "firejail.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
void check_output(int argc, char **argv) {
int i;
char *outfile = NULL;
// drop_privs(0);
int found = 0;
for (i = 1; i < argc; i++) {
if (strncmp(argv[i], "--output=", 9) == 0) {
found = 1;
outfile = argv[i] + 9;
// do not accept directories, links, and files with ".."
if (strstr(outfile, "..") || is_link(outfile) || is_dir(outfile)) {
fprintf(stderr, "Error: invalid output file. Links, directories and files with \"..\" are not allowed.\n");
exit(1);
}
struct stat s;
if (stat(outfile, &s) == 0) {
// check permissions
if (s.st_uid != getuid() || s.st_gid != getgid()) {
fprintf(stderr, "Error: the output file needs to be owned by the current user.\n");
exit(1);
}
// check hard links
if (s.st_nlink != 1) {
fprintf(stderr, "Error: no hard links allowed.\n");
exit(1);
}
}
// drop privileges and try to open the file for writing
drop_privs(0);
/* coverity[toctou] */
FILE *fp = fopen(outfile, "a");
if (!fp) {
fprintf(stderr, "Error: cannot open output file %s\n", outfile);
exit(1);
}
fclose(fp);
break;
}
}
if (!found)
return;
// build the new command line
int len = 0;
for (i = 0; i < argc; i++) {
len += strlen(argv[i]) + 1; // + ' '
}
len += 50 + strlen(outfile); // tee command
char *cmd = malloc(len + 1); // + '\0'
if (!cmd)
errExit("malloc");
char *ptr = cmd;
for (i = 0; i < argc; i++) {
if (strncmp(argv[i], "--output=", 9) == 0)
continue;
ptr += sprintf(ptr, "%s ", argv[i]);
}
sprintf(ptr, "| %s/lib/firejail/ftee %s", PREFIX, outfile);
// run command
char *a[4];
a[0] = "/bin/bash";
a[1] = "-c";
a[2] = cmd;
a[3] = NULL;
execvp(a[0], a);
perror("execvp");
exit(1);
}

444
src/firejail/profile.c Normal file
View file

@ -0,0 +1,444 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firejail.h"
#include <dirent.h>
#include <sys/stat.h>
#define MAX_READ 8192 // line buffer for profile files
// find and read the profile specified by name from dir directory
int profile_find(const char *name, const char *dir) {
assert(name);
assert(dir);
int rv = 0;
DIR *dp;
char *pname;
if (asprintf(&pname, "%s.profile", name) == -1)
errExit("asprintf");
dp = opendir (dir);
if (dp != NULL) {
struct dirent *ep;
while ((ep = readdir(dp)) != NULL) {
if (strcmp(ep->d_name, pname) == 0) {
if (arg_debug)
printf("Found %s profile in %s directory\n", name, dir);
char *etcpname;
if (asprintf(&etcpname, "%s/%s", dir, pname) == -1)
errExit("asprintf");
profile_read(etcpname, NULL, NULL);
free(etcpname);
rv = 1;
break;
}
}
(void) closedir (dp);
}
free(pname);
return rv;
}
//***************************************************
// run-time profiles
//***************************************************
static void check_file_name(char *ptr, int lineno) {
if (strncmp(ptr, "${HOME}", 7) == 0)
ptr += 7;
else if (strncmp(ptr, "${PATH}", 7) == 0)
ptr += 7;
int len = strlen(ptr);
// file globbing ('*') is allowed
if (strcspn(ptr, "\\&!?\"'<>%^(){}[];, ") != len) {
if (lineno == 0)
fprintf(stderr, "Error: \"%s\" is an invalid filename\n", ptr);
else
fprintf(stderr, "Error: line %d in the custom profile is invalid\n", lineno);
exit(1);
}
}
// check profile line; if line == 0, this was generated from a command line option
// return 1 if the command is to be added to the linked list of profile commands
// return 0 if the command was already executed inside the function
int profile_check_line(char *ptr, int lineno) {
// seccomp, caps, private, user namespace
if (strcmp(ptr, "noroot") == 0) {
check_user_namespace();
return 0;
}
else if (strcmp(ptr, "seccomp") == 0) {
arg_seccomp = 1;
return 0;
}
else if (strcmp(ptr, "caps") == 0) {
arg_caps_default_filter = 1;
return 0;
}
else if (strcmp(ptr, "caps.drop all") == 0) {
arg_caps_drop_all = 1;
return 0;
}
else if (strcmp(ptr, "shell none") == 0) {
arg_shell_none = 1;
return 0;
}
else if (strcmp(ptr, "private") == 0) {
arg_private = 1;
return 0;
}
else if (strcmp(ptr, "private-dev") == 0) {
arg_private_dev = 1;
return 0;
}
else if (strcmp(ptr, "nogroups") == 0) {
arg_nogroups = 1;
return 0;
}
else if (strcmp(ptr, "netfilter") == 0) {
arg_netfilter = 1;
return 0;
}
else if (strncmp(ptr, "netfilter ", 10) == 0) {
arg_netfilter = 1;
arg_netfilter_file = strdup(ptr + 10);
if (!arg_netfilter_file)
errExit("strdup");
check_netfilter_file(arg_netfilter_file);
return 0;
}
// seccomp drop list on top of default list
if (strncmp(ptr, "seccomp ", 8) == 0) {
arg_seccomp = 1;
#ifdef HAVE_SECCOMP
arg_seccomp_list = strdup(ptr + 8);
if (!arg_seccomp_list)
errExit("strdup");
#endif
return 0;
}
// seccomp drop list without default list
if (strncmp(ptr, "seccomp.drop ", 13) == 0) {
arg_seccomp = 1;
#ifdef HAVE_SECCOMP
arg_seccomp_list_drop = strdup(ptr + 13);
if (!arg_seccomp_list_drop)
errExit("strdup");
#endif
return 0;
}
// seccomp keep list
if (strncmp(ptr, "seccomp.keep ", 13) == 0) {
arg_seccomp = 1;
#ifdef HAVE_SECCOMP
arg_seccomp_list_keep= strdup(ptr + 13);
if (!arg_seccomp_list_keep)
errExit("strdup");
#endif
return 0;
}
// caps drop list
if (strncmp(ptr, "caps.drop ", 10) == 0) {
arg_caps_drop = 1;
arg_caps_list = strdup(ptr + 10);
if (!arg_caps_list)
errExit("strdup");
// verify seccomp list and exit if problems
if (caps_check_list(arg_caps_list, NULL))
exit(1);
return 0;
}
// caps keep list
if (strncmp(ptr, "caps.keep ", 10) == 0) {
arg_caps_keep = 1;
arg_caps_list = strdup(ptr + 10);
if (!arg_caps_list)
errExit("strdup");
// verify seccomp list and exit if problems
if (caps_check_list(arg_caps_list, NULL))
exit(1);
return 0;
}
// dns
if (strncmp(ptr, "dns ", 4) == 0) {
uint32_t dns;
if (atoip(ptr + 4, &dns)) {
fprintf(stderr, "Error: invalid DNS server IP address\n");
return 1;
}
if (cfg.dns1 == 0)
cfg.dns1 = dns;
else if (cfg.dns2 == 0)
cfg.dns2 = dns;
else if (cfg.dns3 == 0)
cfg.dns3 = dns;
else {
fprintf(stderr, "Error: up to 3 DNS servers can be specified\n");
return 1;
}
return 0;
}
// cpu affinity
if (strncmp(ptr, "cpu ", 4) == 0) {
read_cpu_list(ptr + 4);
return 0;
}
// cgroup
if (strncmp(ptr, "cgroup ", 7) == 0) {
set_cgroup(ptr + 7);
return 0;
}
// private directory
if (strncmp(ptr, "private ", 8) == 0) {
cfg.home_private = ptr + 8;
fs_check_private_dir();
arg_private = 1;
return 0;
}
// private list of files and directories
if (strncmp(ptr, "private.keep ", 13) == 0) {
cfg.home_private_keep = ptr + 13;
fs_check_home_list();
arg_private = 1;
return 0;
}
// filesystem bind
if (strncmp(ptr, "bind ", 5) == 0) {
if (getuid() != 0) {
fprintf(stderr, "Error: --bind option is available only if running as root\n");
exit(1);
}
// extract two directories
char *dname1 = ptr + 5;
char *dname2 = split_comma(dname1); // this inserts a '0 to separate the two dierctories
if (dname2 == NULL) {
fprintf(stderr, "Error: mising second directory for bind\n");
exit(1);
}
// check directories
check_file_name(dname1, lineno);
check_file_name(dname2, lineno);
if (strstr(dname1, "..") || strstr(dname2, "..")) {
fprintf(stderr, "Error: invalid file name.\n");
exit(1);
}
// insert comma back
*(dname2 - 1) = ',';
return 1;
}
// rlimit
if (strncmp(ptr, "rlimit", 6) == 0) {
if (strncmp(ptr, "rlimit-nofile ", 14) == 0) {
ptr += 14;
if (not_unsigned(ptr)) {
fprintf(stderr, "Invalid rlimit option on line %d\n", lineno);
exit(1);
}
sscanf(ptr, "%u", &cfg.rlimit_nofile);
arg_rlimit_nofile = 1;
}
else if (strncmp(ptr, "rlimit-nproc ", 13) == 0) {
ptr += 13;
if (not_unsigned(ptr)) {
fprintf(stderr, "Invalid rlimit option on line %d\n", lineno);
exit(1);
}
sscanf(ptr, "%u", &cfg.rlimit_nproc);
arg_rlimit_nproc = 1;
}
else if (strncmp(ptr, "rlimit-fsize ", 13) == 0) {
ptr += 13;
if (not_unsigned(ptr)) {
fprintf(stderr, "Invalid rlimit option on line %d\n", lineno);
exit(1);
}
sscanf(ptr, "%u", &cfg.rlimit_fsize);
arg_rlimit_fsize = 1;
}
else if (strncmp(ptr, "rlimit-sigpending ", 18) == 0) {
ptr += 18;
if (not_unsigned(ptr)) {
fprintf(stderr, "Invalid rlimit option on line %d\n", lineno);
exit(1);
}
sscanf(ptr, "%u", &cfg.rlimit_sigpending);
arg_rlimit_sigpending = 1;
}
else {
fprintf(stderr, "Invalid rlimit option on line %d\n", lineno);
exit(1);
}
return 0;
}
// rest of filesystem
if (strncmp(ptr, "blacklist ", 10) == 0)
ptr += 10;
else if (strncmp(ptr, "read-only ", 10) == 0)
ptr += 10;
else if (strncmp(ptr, "tmpfs ", 6) == 0)
ptr += 6;
else {
if (lineno == 0)
fprintf(stderr, "Error: \"%s\" as a command line option is invalid\n", ptr);
else
fprintf(stderr, "Error: line %d in the custom profile is invalid\n", lineno);
exit(1);
}
// some characters just don't belong in filenames
check_file_name(ptr, lineno);
if (strstr(ptr, "..")) {
if (lineno == 0)
fprintf(stderr, "Error: \"%s\" is an invalid filename\n", ptr);
else
fprintf(stderr, "Error: line %d in the custom profile is invalid\n", lineno);
exit(1);
}
return 1;
}
// add a profile entry in cfg.profile list; use str to populate the list
void profile_add(char *str) {
ProfileEntry *prf = malloc(sizeof(ProfileEntry));
if (!prf)
errExit("malloc");
prf->next = NULL;
prf->data = str;
// add prf to the list
if (cfg.profile == NULL) {
cfg.profile = prf;
return;
}
ProfileEntry *ptr = cfg.profile;
while (ptr->next != NULL)
ptr = ptr->next;
ptr->next = prf;
}
// read a profile file
static int include_level = 0;
// skip1, skip2 - if the string is found in the line, the line is not interpreted
void profile_read(const char *fname, const char *skip1, const char *skip2) {
// exit program if maximum include level was reached
if (include_level > MAX_INCLUDE_LEVEL) {
fprintf(stderr, "Error: maximum profile include level was reached\n");
exit(1);
}
if (strlen(fname) == 0) {
fprintf(stderr, "Error: invalid profile file\n");
exit(1);
}
// open profile file:
FILE *fp = fopen(fname, "r");
if (fp == NULL) {
fprintf(stderr, "Error: cannot open profile file\n");
exit(1);
}
fprintf(stderr, "Reading profile %s\n", fname);
// read the file line by line
char buf[MAX_READ + 1];
int lineno = 0;
while (fgets(buf, MAX_READ, fp)) {
++lineno;
// remove empty space - ptr in allocated memory
char *ptr = line_remove_spaces(buf);
if (ptr == NULL)
continue;
// comments
if (*ptr == '#' || *ptr == '\0') {
free(ptr);
continue;
}
// process include
if (strncmp(ptr, "include ", 8) == 0) {
include_level++;
// extract profile filename and new skip params
char *newprofile = ptr + 8; // profile name
char *newskip1 = NULL; // new skip1
char *newskip2 = NULL; // new skip2
char *p = newprofile;
while (*p != '\0') {
if (*p == ' ') {
*p = '\0';
if (newskip1 == NULL)
newskip1 = p + 1;
else if (newskip2 == NULL)
newskip2 = p + 1;
}
p++;
}
// recursivity
profile_read(newprofile, newskip1, newskip2);
include_level--;
free(ptr);
continue;
}
// skip
if (skip1) {
if (strstr(ptr, skip1)) {
free(ptr);
continue;
}
}
if (skip2) {
if (strstr(ptr, skip2)) {
free(ptr);
continue;
}
}
// verify syntax, exit in case of error
if (profile_check_line(ptr, lineno))
profile_add(ptr);
}
fclose(fp);
}

View file

@ -0,0 +1,96 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firejail.h"
#define MAX_READ 4096 // maximum line length
char *restricted_user = NULL;
int restricted_shell(const char *user) {
assert(user);
// open profile file:
FILE *fp = fopen("/etc/firejail/login.users", "r");
if (fp == NULL)
return 0;
int lineno = 0;
char buf[MAX_READ];
while (fgets(buf, MAX_READ, fp)) {
lineno++;
// remove empty spaces at the beginning of the line
char *ptr = buf;
while (*ptr == ' ' || *ptr == '\t') {
ptr++;
}
if (*ptr == '\n' || *ptr == '#')
continue;
// parse line
char *usr = ptr;
char *args = strchr(usr, ':');
if (args == NULL) {
fprintf(stderr, "Error: users.conf line %d\n", lineno);
exit(1);
}
*args = '\0';
args++;
ptr = strchr(args, '\n');
if (ptr)
*ptr = '\0';
if (strcmp(user, usr) == 0) {
restricted_user = strdup(user);
// extract program arguments
fullargv[0] = "firejail";
int i;
ptr = args;
for (i = 1; i < MAX_ARGS; i++) {
fullargv[i] = ptr;
while (*ptr != ' ' && *ptr != '\t' && *ptr != '\0')
ptr++;
if (*ptr != '\0') {
*ptr ='\0';
fullargv[i] = strdup(fullargv[i]);
if (fullargv[i] == NULL) {
fprintf(stderr, "Error: cannot allocate memory\n");
exit(1);
}
ptr++;
while (*ptr == ' ' || *ptr == '\t')
ptr++;
if (*ptr != '\0')
continue;
}
fullargv[i] = strdup(fullargv[i]);
fclose(fp);
return i + 1;
}
fprintf(stderr, "Error: too many program arguments in users.conf line %d\n", lineno);
exit(1);
}
}
fclose(fp);
return 0;
}

62
src/firejail/rlimit.c Normal file
View file

@ -0,0 +1,62 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firejail.h"
#include <sys/time.h>
#include <sys/resource.h>
void set_rlimits(void) {
// resource limits
struct rlimit rl;
if (arg_rlimit_nofile) {
rl.rlim_cur = (rlim_t) cfg.rlimit_nofile;
rl.rlim_max = (rlim_t) cfg.rlimit_nofile;
if (setrlimit(RLIMIT_NOFILE, &rl) == -1)
errExit("setrlimit");
if (arg_debug)
printf("Config rlimit: number of open file descriptors %u\n", cfg.rlimit_nofile);
}
if (arg_rlimit_nproc) {
rl.rlim_cur = (rlim_t) cfg.rlimit_nproc;
rl.rlim_max = (rlim_t) cfg.rlimit_nproc;
if (setrlimit(RLIMIT_NPROC, &rl) == -1)
errExit("setrlimit");
if (arg_debug)
printf("Config rlimit: number of processes %u\n", cfg.rlimit_nproc);
}
if (arg_rlimit_fsize) {
rl.rlim_cur = (rlim_t) cfg.rlimit_fsize;
rl.rlim_max = (rlim_t) cfg.rlimit_fsize;
if (setrlimit(RLIMIT_FSIZE, &rl) == -1)
errExit("setrlimit");
if (arg_debug)
printf("Config rlimit: maximum file size %u\n", cfg.rlimit_fsize);
}
if (arg_rlimit_sigpending) {
rl.rlim_cur = (rlim_t) cfg.rlimit_sigpending;
rl.rlim_max = (rlim_t) cfg.rlimit_sigpending;
if (setrlimit(RLIMIT_SIGPENDING, &rl) == -1)
errExit("setrlimit");
if (arg_debug)
printf("Config rlimit: maximum number of signals pending %u\n", cfg.rlimit_sigpending);
}
}

490
src/firejail/sandbox.c Normal file
View file

@ -0,0 +1,490 @@
/*
* Copyright (C) 2014, 2015 Firejail Authors
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firejail.h"
#include <sys/mount.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/prctl.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sched.h>
#ifndef CLONE_NEWUSER
#define CLONE_NEWUSER 0x10000000
#endif
static void set_caps(void) {
if (arg_caps_drop_all)
caps_drop_all();
else if (arg_caps_drop)
caps_drop_list(arg_caps_list);
else if (arg_caps_keep)
caps_keep_list(arg_caps_list);
else if (arg_caps_default_filter)
caps_default_filter();
}
void save_nogroups(void) {
if (arg_nogroups == 0)
return;
char *fname;
if (asprintf(&fname, "%s/groups", MNT_DIR) == -1)
errExit("asprintf");
FILE *fp = fopen(fname, "w");
if (fp) {
fprintf(fp, "\n");
fclose(fp);
if (chown(fname, 0, 0) < 0)
errExit("chown");
}
else {
fprintf(stderr, "Error: cannot save nogroups state\n");
free(fname);
exit(1);
}
free(fname);
}
static void sandbox_if_up(Bridge *br) {
assert(br);
if (!br->configured)
return;
char *dev = br->devsandbox;
net_if_up(dev);
if (br->arg_ip_none == 1); // do nothing
else if (br->arg_ip_none == 0 && br->macvlan == 0) {
if (br->ipsandbox == br->ip) {
fprintf(stderr, "Error: %d.%d.%d.%d is interface %s address.\n", PRINT_IP(br->ipsandbox), br->dev);
exit(1);
}
// just assign the address
assert(br->ipsandbox);
if (arg_debug)
printf("Configuring %d.%d.%d.%d address on interface %s\n", PRINT_IP(br->ipsandbox), dev);
net_if_ip(dev, br->ipsandbox, br->mask);
net_if_up(dev);
}
else if (br->arg_ip_none == 0 && br->macvlan == 1) {
// reassign the macvlan address
if (br->ipsandbox == 0)
// ip address assigned by arp-scan for a macvlan device
br->ipsandbox = arp_assign(dev, br); //br->ip, br->mask);
else {
if (br->ipsandbox == br->ip) {
fprintf(stderr, "Error: %d.%d.%d.%d is interface %s address.\n", PRINT_IP(br->ipsandbox), br->dev);
exit(1);
}
uint32_t rv = arp_check(dev, br->ipsandbox, br->ip);
if (rv) {
fprintf(stderr, "Error: the address %d.%d.%d.%d is already in use.\n", PRINT_IP(br->ipsandbox));
exit(1);
}
}
if (arg_debug)
printf("Configuring %d.%d.%d.%d address on interface %s\n", PRINT_IP(br->ipsandbox), dev);
net_if_ip(dev, br->ipsandbox, br->mask);
net_if_up(dev);
}
}
static void chk_chroot(void) {
// if we are starting firejail inside some other container technology, we don't care about this
char *mycont = getenv("container");
if (mycont)
return;
// check if this is a regular chroot
struct stat s;
if (stat("/", &s) == 0) {
if (s.st_ino != 2)
return;
}
fprintf(stderr, "Error: cannot mount filesystem as slave\n");
exit(1);
}
int sandbox(void* sandbox_arg) {
pid_t child_pid = getpid();
if (arg_debug)
printf("Initializing child process\n");
// close each end of the unused pipes
close(parent_to_child_fds[1]);
close(child_to_parent_fds[0]);
// wait for parent to do base setup
wait_for_other(parent_to_child_fds[0]);
if (arg_debug && child_pid == 1)
printf("PID namespace installed\n");
//****************************
// set hostname
//****************************
if (cfg.hostname) {
if (sethostname(cfg.hostname, strlen(cfg.hostname)) < 0)
errExit("sethostname");
}
//****************************
// mount namespace
//****************************
// mount events are not forwarded between the host the sandbox
if (mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL) < 0) {
chk_chroot();
}
//****************************
// netfilter
//****************************
if (arg_netfilter && any_bridge_configured()) { // assuming by default the client filter
netfilter(arg_netfilter_file);
}
//****************************
// trace pre-install
//****************************
if (arg_trace)
fs_trace_preload();
//****************************
// configure filesystem
//****************************
#ifdef HAVE_CHROOT
if (cfg.chrootdir) {
fs_chroot(cfg.chrootdir);
// force caps and seccomp if not started as root
if (getuid() != 0) {
// force default seccomp inside the chroot, no keep or drop list
// the list build on top of the default drop list is kept intact
arg_seccomp = 1;
if (arg_seccomp_list_drop) {
free(arg_seccomp_list_drop);
arg_seccomp_list_drop = NULL;
}
if (arg_seccomp_list_keep) {
free(arg_seccomp_list_keep);
arg_seccomp_list_keep = NULL;
}
// disable all capabilities
if (arg_caps_default_filter || arg_caps_list)
fprintf(stderr, "Warning: all capabilities disabled for a regular user during chroot\n");
arg_caps_drop_all = 1;
// drop all supplementary groups; /etc/group file inside chroot
// is controlled by a regular usr
arg_nogroups = 1;
printf("Dropping all Linux capabilities and enforcing default seccomp filter\n");
}
//****************************
// trace pre-install, this time inside chroot
//****************************
if (arg_trace)
fs_trace_preload();
}
else
#endif
if (arg_overlay)
fs_overlayfs();
else
fs_basic_fs();
//****************************
// set hostname in /etc/hostname
//****************************
if (cfg.hostname) {
fs_hostname(cfg.hostname);
}
//****************************
// apply the profile file
//****************************
if (cfg.profile)
fs_blacklist(cfg.homedir);
//****************************
// private mode
//****************************
if (arg_private) {
if (cfg.home_private) // --private=
fs_private_homedir();
else if (cfg.home_private_keep) // --private.keep=
fs_private_home_list();
else // --private
fs_private();
}
if (arg_private_dev)
fs_private_dev();
//****************************
// install trace
//****************************
if (arg_trace)
fs_trace();
//****************************
// update /proc, /dev, /boot directorymy
//****************************
fs_proc_sys_dev_boot();
//****************************
// networking
//****************************
if (arg_nonetwork) {
net_if_up("lo");
}
else if (any_bridge_configured()) {
// configure lo and eth0...eth3
net_if_up("lo");
if (mac_not_zero(cfg.bridge0.macsandbox))
net_config_mac(cfg.bridge0.devsandbox, cfg.bridge0.macsandbox);
sandbox_if_up(&cfg.bridge0);
if (mac_not_zero(cfg.bridge1.macsandbox))
net_config_mac(cfg.bridge1.devsandbox, cfg.bridge1.macsandbox);
sandbox_if_up(&cfg.bridge1);
if (mac_not_zero(cfg.bridge2.macsandbox))
net_config_mac(cfg.bridge2.devsandbox, cfg.bridge2.macsandbox);
sandbox_if_up(&cfg.bridge2);
if (mac_not_zero(cfg.bridge3.macsandbox))
net_config_mac(cfg.bridge3.devsandbox, cfg.bridge3.macsandbox);
sandbox_if_up(&cfg.bridge3);
// add a default route
if (cfg.defaultgw) {
// set the default route
if (net_add_route(0, 0, cfg.defaultgw))
fprintf(stderr, "Warning: cannot configure default route\n");
}
if (arg_debug)
printf("Network namespace enabled\n");
}
// if any dns server is configured, it is time to set it now
fs_resolvconf();
// print network configuration
if (any_bridge_configured() || cfg.defaultgw || cfg.dns1) {
printf("\n");
if (any_bridge_configured())
net_ifprint();
if (cfg.defaultgw != 0)
printf("Default gateway %d.%d.%d.%d\n", PRINT_IP(cfg.defaultgw));
if (cfg.dns1 != 0)
printf("DNS server %d.%d.%d.%d\n", PRINT_IP(cfg.dns1));
if (cfg.dns2 != 0)
printf("DNS server %d.%d.%d.%d\n", PRINT_IP(cfg.dns2));
if (cfg.dns3 != 0)
printf("DNS server %d.%d.%d.%d\n", PRINT_IP(cfg.dns3));
printf("\n");
}
//****************************
// start executable
//****************************
prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0); // kill the child in case the parent died
int cwd = 0;
if (cfg.cwd) {
if (chdir(cfg.cwd) == 0)
cwd = 1;
}
if (!cwd) {
if (chdir("/") < 0)
errExit("chdir");
if (cfg.homedir) {
struct stat s;
if (stat(cfg.homedir, &s) == 0) {
/* coverity[toctou] */
if (chdir(cfg.homedir) < 0)
errExit("chdir");
}
}
}
// set environment
// fix qt 4.8
if (setenv("QT_X11_NO_MITSHM", "1", 1) < 0)
errExit("setenv");
if (setenv("container", "firejail", 1) < 0) // LXC sets container=lxc,
errExit("setenv");
if (arg_zsh && setenv("SHELL", "/usr/bin/zsh", 1) < 0)
errExit("setenv");
if (arg_csh && setenv("SHELL", "/bin/csh", 1) < 0)
errExit("setenv");
if (cfg.shell && setenv("SHELL", cfg.shell, 1) < 0)
errExit("setenv");
// set prompt color to green
//export PS1='\[\e[1;32m\][\u@\h \W]\$\[\e[0m\] '
if (setenv("PROMPT_COMMAND", "export PS1=\"\\[\\e[1;32m\\][\\u@\\h \\W]\\$\\[\\e[0m\\] \"", 1) < 0)
errExit("setenv");
// set capabilities
if (!arg_noroot)
set_caps();
// set rlimits
set_rlimits();
// set seccomp
#ifdef HAVE_SECCOMP
// if a keep list is available, disregard the drop list
if (arg_seccomp == 1) {
if (arg_seccomp_list_keep)
seccomp_filter_keep(); // this will also save the fmyilter to MNT_DIR/seccomp file
else
seccomp_filter_drop(); // this will also save the filter to MNT_DIR/seccomp file
}
#endif
// set cpu affinity
if (cfg.cpus) {
save_cpu(); // save cpu affinity mask to MNT_DIR/cpu file
set_cpu_affinity();
}
// save cgroup in MNT_DIR/cgroup file
if (cfg.cgroup)
save_cgroup();
//****************************************
// drop privileges or create a new user namespace
//****************************************
save_nogroups();
if (arg_noroot) {
int rv = unshare(CLONE_NEWUSER);
if (rv == -1) {
fprintf(stderr, "Warning: cannot mount a new user namespace\n");
perror("unshare");
drop_privs(arg_nogroups);
}
}
else
drop_privs(arg_nogroups);
// notify parent that new user namespace has been created so a proper
// UID/GID map can be setup
notify_other(child_to_parent_fds[1]);
close(child_to_parent_fds[1]);
// wait for parent to finish setting up a proper UID/GID map
wait_for_other(parent_to_child_fds[0]);
close(parent_to_child_fds[0]);
// somehow, the new user namespace resets capabilities;
// we need to do them again
if (arg_noroot) {
set_caps();
if (arg_debug)
printf("User namespace (noroot) installed\n");
}
//****************************************
// start the program without using a shell
//****************************************
if (arg_shell_none) {
if (arg_debug) {
int i;
for (i = cfg.original_program_index; i < cfg.original_argc; i++) {
if (cfg.original_argv[i] == NULL)
break;
printf("execvp argument %d: %s\n", i - cfg.original_program_index, cfg.original_argv[i]);
}
}
if (!arg_command)
printf("Child process initialized\n");
execvp(cfg.original_argv[cfg.original_program_index], &cfg.original_argv[cfg.original_program_index + 1]);
}
//****************************************
// start the program using a shell
//****************************************
else {
// choose the shell requested by the user, or use bash as default
char *sh;
if (cfg.shell)
sh = cfg.shell;
else if (arg_zsh)
sh = "/usr/bin/zsh";
else if (arg_csh)
sh = "/bin/csh";
else
sh = "/bin/bash";
char *arg[5];
int index = 0;
arg[index++] = sh;
arg[index++] = "-c";
assert(cfg.command_line);
if (arg_debug)
printf("Starting %s\n", cfg.command_line);
if (arg_doubledash)
arg[index++] = "--";
arg[index++] = cfg.command_line;
arg[index] = NULL;
assert(index < 5);
if (arg_debug) {
char *msg;
if (asprintf(&msg, "sandbox %d, execvp into %s", sandbox_pid, cfg.command_line) == -1)
errExit("asprintf");
logmsg(msg);
free(msg);
}
if (arg_debug) {
int i;
for (i = 0; i < 5; i++) {
if (arg[i] == NULL)
break;
printf("execvp argument %d: %s\n", i, arg[i]);
}
}
if (!arg_command)
printf("Child process initialized\n");
execvp(sh, arg);
}
perror("execvp");
return 0;
}

658
src/firejail/seccomp.c Normal file
View file

@ -0,0 +1,658 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/* default seccomp filter
// seccomp
struct sock_filter filter[] = {
VALIDATE_ARCHITECTURE,
EXAMINE_SYSCALL,
BLACKLIST(SYS_mount), // mount/unmount filesystems
BLACKLIST(SYS_umount2),
BLACKLIST(SYS_ptrace), // trace processes
BLACKLIST(SYS_kexec_load), // loading a different kernel
BLACKLIST(SYS_open_by_handle_at), // open by handle
BLACKLIST(SYS_init_module), // kernel module handling
#ifdef SYS_finit_module // introduced in 2013
BLACKLIST(SYS_finit_module),
#endif
BLACKLIST(SYS_delete_module),
BLACKLIST(SYS_iopl), // io permisions
#ifdef SYS_ioperm
BLACKLIST(SYS_ioperm),
#endif
SYS_iopl
BLACKLIST(SYS_iopl), // io permisions
#endif
#ifdef SYS_ni_syscall), // new io permisions call on arm devices
BLACKLIST(SYS_ni_syscall),
#endif
BLACKLIST(SYS_swapon), // swap on/off
BLACKLIST(SYS_swapoff),
BLACKLIST(SYS_syslog), // kernel printk control
RETURN_ALLOW
};
*/
#ifdef HAVE_SECCOMP
#include "firejail.h"
#include <errno.h>
#include <linux/filter.h>
#include <sys/syscall.h>
#include <linux/capability.h>
#include <linux/audit.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/prctl.h>
#ifndef PR_SET_NO_NEW_PRIVS
# define PR_SET_NO_NEW_PRIVS 38
#endif
#if HAVE_SECCOMP_H
#include <linux/seccomp.h>
#else
#define SECCOMP_MODE_FILTER 2
#define SECCOMP_RET_KILL 0x00000000U
#define SECCOMP_RET_TRAP 0x00030000U
#define SECCOMP_RET_ALLOW 0x7fff0000U
#define SECCOMP_RET_ERRNO 0x00050000U
#define SECCOMP_RET_DATA 0x0000ffffU
struct seccomp_data {
int nr;
__u32 arch;
__u64 instruction_pointer;
__u64 args[6];
};
#endif
#if defined(__i386__)
# define ARCH_NR AUDIT_ARCH_I386
#elif defined(__x86_64__)
# define ARCH_NR AUDIT_ARCH_X86_64
#elif defined(__arm__)
# define ARCH_NR AUDIT_ARCH_ARM
#else
# warning "Platform does not support seccomp filter yet"
# define ARCH_NR 0
#endif
#define VALIDATE_ARCHITECTURE \
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, (offsetof(struct seccomp_data, arch))), \
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ARCH_NR, 1, 0), \
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL)
#define EXAMINE_SYSCALL BPF_STMT(BPF_LD+BPF_W+BPF_ABS, \
(offsetof(struct seccomp_data, nr)))
#define BLACKLIST(syscall_nr) \
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, syscall_nr, 0, 1), \
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL)
#define WHITELIST(syscall_nr) \
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, syscall_nr, 0, 1), \
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW)
#define RETURN_ALLOW \
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW)
#define KILL_PROCESS \
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL)
#define SECSIZE 128 // initial filter size
static struct sock_filter *sfilter = NULL;
static int sfilter_alloc_size = 0;
static int sfilter_index = 0;
// debug filter
void filter_debug(void) {
// start filter
struct sock_filter filter[] = {
VALIDATE_ARCHITECTURE,
EXAMINE_SYSCALL
};
// print sizes
printf("SECCOMP Filter:\n");
if (sfilter == NULL) {
printf("SECCOMP filter not allocated\n");
return;
}
if (sfilter_index < 4)
return;
// test the start of the filter
if (memcmp(sfilter, filter, sizeof(filter)) == 0) {
printf(" VALIDATE_ARCHITECTURE\n");
printf(" EXAMINE_SYSCAL\n");
}
// loop trough blacklists
int i = 4;
while (i < sfilter_index) {
// minimal parsing!
unsigned char *ptr = (unsigned char *) &sfilter[i];
int *nr = (int *) (ptr + 4);
if (*ptr == 0x15 && *(ptr +14) == 0xff && *(ptr + 15) == 0x7f ) {
printf(" WHITELIST %d %s\n", *nr, syscall_find_nr(*nr));
i += 2;
}
else if (*ptr == 0x15 && *(ptr +14) == 0 && *(ptr + 15) == 0) {
printf(" BLACKLIST %d %s\n", *nr, syscall_find_nr(*nr));
i += 2;
}
else if (*ptr == 0x06 && *(ptr +6) == 0 && *(ptr + 7) == 0 ) {
printf(" KILL_PROCESS\n");
i++;
}
else if (*ptr == 0x06 && *(ptr +6) == 0xff && *(ptr + 7) == 0x7f ) {
printf(" RETURN_ALLOW\n");
i++;
}
else {
printf(" UNKNOWN ENTRY!!!\n");
i++;
}
}
}
// initialize filter
static void filter_init(void) {
if (sfilter) {
assert(0);
return;
}
if (arg_debug)
printf("Initialize seccomp filter\n");
// allocate a filter of SECSIZE
sfilter = malloc(sizeof(struct sock_filter) * SECSIZE);
if (!sfilter)
errExit("malloc");
memset(sfilter, 0, sizeof(struct sock_filter) * SECSIZE);
sfilter_alloc_size = SECSIZE;
// copy the start entries
struct sock_filter filter[] = {
VALIDATE_ARCHITECTURE,
EXAMINE_SYSCALL
};
sfilter_index = sizeof(filter) / sizeof(struct sock_filter);
memcpy(sfilter, filter, sizeof(filter));
}
static void filter_realloc(void) {
assert(sfilter);
assert(sfilter_alloc_size);
assert(sfilter_index);
if (arg_debug)
printf("Allocating more seccomp filter entries\n");
// allocate the new memory
struct sock_filter *old = sfilter;
sfilter = malloc(sizeof(struct sock_filter) * (sfilter_alloc_size + SECSIZE));
if (!sfilter)
errExit("malloc");
memset(sfilter, 0, sizeof(struct sock_filter) * (sfilter_alloc_size + SECSIZE));
// copy old filter
memcpy(sfilter, old, sizeof(struct sock_filter) * sfilter_alloc_size);
sfilter_alloc_size += SECSIZE;
}
static void filter_add_whitelist(int syscall) {
assert(sfilter);
assert(sfilter_alloc_size);
assert(sfilter_index);
if (arg_debug)
printf("Whitelisting syscall %d %s\n", syscall, syscall_find_nr(syscall));
if ((sfilter_index + 2) > sfilter_alloc_size)
filter_realloc();
struct sock_filter filter[] = {
WHITELIST(syscall)
};
#if 0
{
int i;
unsigned char *ptr = (unsigned char *) &filter[0];
for (i = 0; i < sizeof(filter); i++, ptr++)
printf("%x, ", (*ptr) & 0xff);
printf("\n");
}
#endif
memcpy(&sfilter[sfilter_index], filter, sizeof(filter));
sfilter_index += sizeof(filter) / sizeof(struct sock_filter);
}
static void filter_add_blacklist(int syscall) {
assert(sfilter);
assert(sfilter_alloc_size);
assert(sfilter_index);
if (arg_debug)
printf("Blacklisting syscall %d %s\n", syscall, syscall_find_nr(syscall));
if ((sfilter_index + 2) > sfilter_alloc_size)
filter_realloc();
struct sock_filter filter[] = {
BLACKLIST(syscall)
};
#if 0
{
int i;
unsigned char *ptr = (unsigned char *) &filter[0];
for (i = 0; i < sizeof(filter); i++, ptr++)
printf("%x, ", (*ptr) & 0xff);
printf("\n");
}
#endif
memcpy(&sfilter[sfilter_index], filter, sizeof(filter));
sfilter_index += sizeof(filter) / sizeof(struct sock_filter);
}
static void filter_end_blacklist(void) {
assert(sfilter);
assert(sfilter_alloc_size);
assert(sfilter_index);
if (arg_debug)
printf("Ending syscall filter\n");
if ((sfilter_index + 2) > sfilter_alloc_size)
filter_realloc();
struct sock_filter filter[] = {
RETURN_ALLOW
};
#if 0
{
int i;
unsigned char *ptr = (unsigned char *) &filter[0];
for (i = 0; i < sizeof(filter); i++, ptr++)
printf("%x, ", (*ptr) & 0xff);
printf("\n");
}
#endif
memcpy(&sfilter[sfilter_index], filter, sizeof(filter));
sfilter_index += sizeof(filter) / sizeof(struct sock_filter);
}
static void filter_end_whitelist(void) {
assert(sfilter);
assert(sfilter_alloc_size);
assert(sfilter_index);
if (arg_debug)
printf("Ending syscall filter\n");
if ((sfilter_index + 2) > sfilter_alloc_size)
filter_realloc();
struct sock_filter filter[] = {
KILL_PROCESS
};
#if 0
{
int i;
unsigned char *ptr = (unsigned char *) &filter[0];
for (i = 0; i < sizeof(filter); i++, ptr++)
printf("%x, ", (*ptr) & 0xff);
printf("\n");
}
#endif
memcpy(&sfilter[sfilter_index], filter, sizeof(filter));
sfilter_index += sizeof(filter) / sizeof(struct sock_filter);
}
// save seccomp filter in /tmp/firejail/mnt/seccomp
static void write_seccomp_file(void) {
fs_build_mnt_dir();
assert(sfilter);
char *fname;
if (asprintf(&fname, "%s/seccomp", MNT_DIR) == -1)
errExit("asprintf");
int fd = open(fname, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR);
if (fd == -1)
errExit("open");
if (arg_debug)
printf("Save seccomp filter, size %lu bytes\n", sfilter_index * sizeof(struct sock_filter));
errno = 0;
ssize_t sz = write(fd, sfilter, sfilter_index * sizeof(struct sock_filter));
if (sz != (sfilter_index * sizeof(struct sock_filter))) {
fprintf(stderr, "Error: cannot save seccomp filter\n");
exit(1);
}
close(fd);
if (chown(fname, 0, 0) < 0)
errExit("chown");
free(fname);
}
// read seccomp filter from /tmp/firejail/mnt/seccomp
static void read_seccomp_file(char *file_name) {
assert(sfilter == NULL && sfilter_index == 0);
char *fname;
if (file_name)
fname = file_name;
else {
if (asprintf(&fname, "%s/seccomp", MNT_DIR) == -1)
errExit("asprintf");
}
// check file
struct stat s;
if (stat(fname, &s) == -1) {
fprintf(stderr, "Error: seccomp file not found\n");
exit(1);
}
ssize_t sz = s.st_size;
if (sz == 0 || (sz % sizeof(struct sock_filter)) != 0) {
fprintf(stderr, "Error: invalid seccomp file\n");
exit(1);
}
sfilter = malloc(sz);
if (!sfilter)
errExit("malloc");
// read file
/* coverity[toctou] */
int fd = open(fname,O_RDONLY);
if (fd == -1)
errExit("open");
errno = 0;
ssize_t size = read(fd, sfilter, sz);
if (size != sz) {
fprintf(stderr, "Error: invalid seccomp file\n");
exit(1);
}
sfilter_index = sz / sizeof(struct sock_filter);
if (arg_debug)
printf("Read seccomp filter, size %lu bytes\n", sfilter_index * sizeof(struct sock_filter));
close(fd);
free(fname);
if (arg_debug)
filter_debug();
}
// drop filter for seccomp option
int seccomp_filter_drop(void) {
filter_init();
// default seccomp
if (arg_seccomp_list_drop == NULL) {
#ifdef SYS_mount
filter_add_blacklist(SYS_mount);
#endif
#ifdef SYS_umount2
filter_add_blacklist(SYS_umount2);
#endif
#ifdef SYS_ptrace
filter_add_blacklist(SYS_ptrace);
#endif
#ifdef SYS_kexec_load
filter_add_blacklist(SYS_kexec_load);
#endif
#ifdef SYS_open_by_handle_at
filter_add_blacklist(SYS_open_by_handle_at);
#endif
#ifdef SYS_init_module
filter_add_blacklist(SYS_init_module);
#endif
#ifdef SYS_finit_module // introduced in 2013
filter_add_blacklist(SYS_finit_module);
#endif
#ifdef SYS_delete_module
filter_add_blacklist(SYS_delete_module);
#endif
#ifdef SYS_iopl
filter_add_blacklist(SYS_iopl);
#endif
#ifdef SYS_ioperm
filter_add_blacklist(SYS_ioperm);
#endif
#ifdef SYS_ni_syscall // new io permisions call on arm devices
filter_add_blacklist(SYS_ni_syscall);
#endif
#ifdef SYS_swapon
filter_add_blacklist(SYS_swapon);
#endif
#ifdef SYS_swapoff
filter_add_blacklist(SYS_swapoff);
#endif
#ifdef SYS_syslog
filter_add_blacklist(SYS_syslog);
#endif
#ifdef SYS_process_vm_readv
filter_add_blacklist(SYS_process_vm_readv);
#endif
#ifdef SYS_process_vm_writev
filter_add_blacklist(SYS_process_vm_writev);
#endif
#ifdef SYS_mknod
filter_add_blacklist(SYS_mknod);
#endif
// new syscalls in 0.9,23
#ifdef SYS_sysfs
filter_add_blacklist(SYS_sysfs);
#endif
#ifdef SYS__sysctl
filter_add_blacklist(SYS__sysctl);
#endif
#ifdef SYS_adjtimex
filter_add_blacklist(SYS_adjtimex);
#endif
#ifdef SYS_clock_adjtime
filter_add_blacklist(SYS_clock_adjtime);
#endif
#ifdef SYS_lookup_dcookie
filter_add_blacklist(SYS_lookup_dcookie);
#endif
#ifdef SYS_perf_event_open
filter_add_blacklist(SYS_perf_event_open);
#endif
#ifdef SYS_fanotify_init
filter_add_blacklist(SYS_fanotify_init);
#endif
#ifdef SYS_kcmp
filter_add_blacklist(SYS_kcmp);
#endif
}
// default seccomp filter with additional drop list
if (arg_seccomp_list && arg_seccomp_list_drop == NULL) {
if (syscall_check_list(arg_seccomp_list, filter_add_blacklist)) {
fprintf(stderr, "Error: cannot load seccomp filter\n");
exit(1);
}
}
// drop list
else if (arg_seccomp_list == NULL && arg_seccomp_list_drop) {
if (syscall_check_list(arg_seccomp_list_drop, filter_add_blacklist)) {
fprintf(stderr, "Error: cannot load seccomp filter\n");
exit(1);
}
}
filter_end_blacklist();
if (arg_debug)
filter_debug();
// save seccomp filter in /tmp/firejail/mnt/seccomp
// in order to use it in --join operations
write_seccomp_file();
struct sock_fprog prog = {
.len = sfilter_index,
.filter = sfilter,
};
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) || prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
fprintf(stderr, "Warning: seccomp disabled, it requires a Linux kernel version 3.5 or newer.\n");
return 1;
}
else if (arg_debug) {
printf("seccomp enabled\n");
}
return 0;
}
// keep filter for seccomp option
int seccomp_filter_keep(void) {
filter_init();
// these 4 syscalls are used by firejail after the seccomp filter is initialized
filter_add_whitelist(SYS_setuid);
filter_add_whitelist(SYS_setgid);
filter_add_whitelist(SYS_setgroups);
filter_add_whitelist(SYS_dup);
// apply keep list
if (arg_seccomp_list_keep) {
if (syscall_check_list(arg_seccomp_list_keep, filter_add_whitelist)) {
fprintf(stderr, "Error: cannot load seccomp filter\n");
exit(1);
}
}
filter_end_whitelist();
if (arg_debug)
filter_debug();
// save seccomp filter in /tmp/firejail/mnt/seccomp
// in order to use it in --join operations
write_seccomp_file();
struct sock_fprog prog = {
.len = sfilter_index,
.filter = sfilter,
};
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) || prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
fprintf(stderr, "Warning: seccomp disabled, it requires a Linux kernel version 3.5 or newer.\n");
return 1;
}
else if (arg_debug) {
printf("seccomp enabled\n");
}
return 0;
}
void seccomp_set(void) {
// read seccomp filter from /tmp/firejail/mnt/seccomp
read_seccomp_file(NULL);
// apply filter
struct sock_fprog prog = {
.len = sfilter_index,
.filter = sfilter,
};
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) || prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
fprintf(stderr, "Warning: seccomp disabled, it requires a Linux kernel version 3.5 or newer.\n");
return;
}
else if (arg_debug) {
printf("seccomp enabled\n");
}
}
void seccomp_print_filter_name(const char *name) {
if (!name || strlen(name) == 0) {
fprintf(stderr, "Error: invalid sandbox name\n");
exit(1);
}
pid_t pid;
if (name2pid(name, &pid)) {
fprintf(stderr, "Error: cannot find sandbox %s\n", name);
exit(1);
}
seccomp_print_filter(pid);
}
void seccomp_print_filter(pid_t pid) {
// if the pid is that of a firejail process, use the pid of the first child process
char *comm = pid_proc_comm(pid);
if (comm) {
// remove \n
char *ptr = strchr(comm, '\n');
if (ptr)
*ptr = '\0';
if (strcmp(comm, "firejail") == 0) {
pid_t child;
if (find_child(pid, &child) == 0) {
pid = child;
}
}
free(comm);
}
// check privileges for non-root users
uid_t uid = getuid();
if (uid != 0) {
struct stat s;
char *dir;
if (asprintf(&dir, "/proc/%u/ns", pid) == -1)
errExit("asprintf");
if (stat(dir, &s) < 0)
errExit("stat");
if (s.st_uid != uid) {
printf("Error: permission denied.\n");
exit(1);
}
}
// find the seccomp filter
char *fname;
if (asprintf(&fname, "/proc/%d/root/tmp/firejail/mnt/seccomp", pid) == -1)
errExit("asprintf");
struct stat s;
if (stat(fname, &s) == -1) {
printf("Cannot access seccomp filter.\n");
exit(1);
}
// read and print the filter
read_seccomp_file(fname);
drop_privs(1);
filter_debug();
exit(0);
}
#endif // HAVE_SECCOMP

98
src/firejail/shutdown.c Normal file
View file

@ -0,0 +1,98 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firejail.h"
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/prctl.h>
void shut_name(const char *name) {
if (!name || strlen(name) == 0) {
fprintf(stderr, "Error: invalid sandbox name\n");
exit(1);
}
pid_t pid;
if (name2pid(name, &pid)) {
fprintf(stderr, "Error: cannot find sandbox %s\n", name);
exit(1);
}
shut(pid);
}
void shut(pid_t pid) {
pid_t parent = pid;
// if the pid is that of a firejail process, use the pid of a child process inside the sandbox
char *comm = pid_proc_comm(pid);
if (comm) {
// remove \n
char *ptr = strchr(comm, '\n');
if (ptr)
*ptr = '\0';
if (strcmp(comm, "firejail") == 0) {
pid_t child;
if (find_child(pid, &child) == 0) {
pid = child;
printf("Switching to pid %u, the first child process inside the sandbox\n", (unsigned) pid);
}
}
free(comm);
}
// check privileges for non-root users
uid_t uid = getuid();
if (uid != 0) {
struct stat s;
char *dir;
if (asprintf(&dir, "/proc/%u/ns", pid) == -1)
errExit("asprintf");
if (stat(dir, &s) < 0)
errExit("stat");
if (s.st_uid != uid) {
fprintf(stderr, "Error: permission is denied to shutdown a sandbox created by a different user.\n");
exit(1);
}
}
printf("Sending SIGTERM to %u\n", pid);
kill(pid, SIGTERM);
sleep(2);
// if the process is still running, terminate it using SIGKILL
// try to open stat file
char *file;
if (asprintf(&file, "/proc/%u/status", pid) == -1) {
perror("asprintf");
exit(1);
}
FILE *fp = fopen(file, "r");
if (!fp)
return;
fclose(fp);
// kill the process and also the parent
printf("Sending SIGKILL to %u\n", pid);
kill(pid, SIGKILL);
if (parent != pid) {
printf("Sending SIGKILL to %u\n", parent);
kill(parent, SIGKILL);
}
}

4942
src/firejail/syscall.c Normal file

File diff suppressed because it is too large Load diff

312
src/firejail/usage.c Normal file
View file

@ -0,0 +1,312 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firejail.h"
void usage(void) {
printf("firejail - version %s\n\n", VERSION);
printf("Firejail is a SUID sandbox program that reduces the risk of security breaches by\n");
printf("restricting the running environment of untrusted applications using Linux\n");
printf("namespaces. It includes a sandbox profile for Mozilla Firefox.\n\n");
printf("\n");
printf("Usage: firejail [options] [program and arguments]\n\n");
printf("\n");
printf("Without any options, the sandbox consists of a filesystem chroot build from the\n");
printf("current system directories mounted read-only, and new PID and IPC\n");
printf("namespaces. If no program is specified as an argument, /bin/bash is started by\n");
printf("default in the sandbox.\n\n");
printf("\n");
printf("Options:\n\n");
printf("\t-- - signal the end of options and disables further option processing.\n\n");
printf("\t--bandwidth=name - set bandwidth limits for the sandbox identified\n");
printf("\t\tby name, see Traffic Shaping section for more details.\n\n");
printf("\t--bandwidth=pid - set bandwidth limits for the sandbox identified\n");
printf("\t\tby PID, see Traffic Shaping section for more details.\n\n");
#ifdef HAVE_BIND
printf("\t--bind=dirname1,dirname2 - mount-bind dirname1 on top of dirname2.\n\n");
printf("\t--bind=filename1,dirname2 - mount-bind filename1 on top of filename2.\n\n");
#endif
printf("\t--blacklist=dirname_or_filename - blacklist directory or file.\n\n");
printf("\t-c - execute command and exit.\n\n");
printf("\t--caps - enable default Linux capabilities filter. The filter disables\n");
printf("\t\tCAP_SYS_MODULE, CAP_SYS_RAWIO, CAP_SYS_BOOT, CAP_SYS_NICE,\n");
printf("\t\tCAP_SYS_TTY_CONFIG, CAP_SYSLOG, CAP_MKNOD, CAP_SYS_ADMIN.\n\n");
printf("\t--caps.drop=all - drop all capabilities.\n\n");
printf("\t--caps.drop=capability,capability,capability - blacklist Linux\n");
printf("\t\tcapabilities filter.\n\n");
printf("\t--caps.keep=capability,capability,capability - whitelist Linux\n");
printf("\t\tcapabilities filter.\n\n");
printf("\t--caps.print=name - print the caps filter for the sandbox identified\n");
printf("\t\tby name.\n\n");
printf("\t--caps.print=pid - print the caps filter for the sandbox identified\n");
printf("\t\tby PID.\n\n");
printf("\t--cgroup=tasks-file - place the sandbox in the specified control group.\n");
printf("\t\ttasks-file is the full path of cgroup tasks file.\n");
printf("\t\tExample: --cgroup=/sys/fs/cgroup/g1/tasks\n\n");
#ifdef HAVE_CHROOT
printf("\t--chroot=dirname - chroot into dirname directory.\n\n");
#endif
printf("\t--cpu=cpu-number,cpu-number - set cpu affinity.\n");
printf("\t\tExample: cpu=0,1,2\n\n");
printf("\t--csh - use /bin/csh as default shell.\n\n");
printf("\t--debug - print sandbox debug messages.\n\n");
printf("\t--debug-syscalls - print all recognized system calls in the current\n");
printf("\t\tFirejail software build and exit.\n\n");
printf("\t--debug-caps - print all recognized capabilities in the current\n");
printf("\t\tFirejail software build and exit.\n\n");
printf("\t--defaultgw=address - use this address as default gateway in the new\n");
printf("\t\tnetwork namespace.\n\n");
printf("\t--dns=address - set a DNS server for the sandbox. Up to three DNS\n");
printf("\t\tservers can be defined.\n\n");
printf("\t--dns.print=name - print DNS configuration for the sandbox identified\n");
printf("\t\tby name.\n\n");
printf("\t--dns.print=pid - print DNS configuration of the sandbox identified.\n");
printf("\t\tby PID.\n\n");
printf("\t--help, -? - this help screen.\n\n");
printf("\t--ip=address - set interface IP address.\n\n");
printf("\t--ip=none - no IP address and no default gateway address are configured\n");
printf("\t\tin the new network namespace. Use this option in case you intend\n");
printf("\t\tto start an external DHCP client in the sandbox.\n\n");
printf("\t--iprange=address,address - configure an IP address in this range\n\n");
printf("\t--ipc-namespace - enable a new IPC namespace if the sandbox was started\n");
printf("\t\tas a regular user. IPC namespace is enabled by default only if\n");
printf("\t\tthe sandbox is started as root.\n\n");
printf("\t--join=name - join the sandbox identified by name.\n\n");
printf("\t--join=pid - join the sandbox identified by PID.\n\n");
printf("\t--list - list all sandboxes.\n\n");
printf("\t--mac=xx:xx:xx:xx:xx:xx - set interface MAC address.\n\n");
printf("\t--name=name - set sandbox hostname.\n\n");
printf("\t--net=bridgename - enable network namespaces and connect to this bridge\n");
printf("\t\tdevice. Unless specified with option --ip and --defaultgw, an\n");
printf("\t\tIP address and a default gateway will be assigned automatically\n");
printf("\t\tto the sandbox. The IP address is checked using ARP before\n");
printf("\t\tassignment. The IP address assigned as default gateway is the\n");
printf("\t\tbridge device IP address. Up to four --net devices can\n");
printf("\t\tbe defined. Mixing bridge and macvlan devices is allowed.\n\n");
printf("\t--net=ethernet_interface - enable network namespaces and connect\n");
printf("\t\tto this ethernet_interface using the standard Linux macvlan\n");
printf("\t\tdriver. Unless specified with option --ip and --defaultgw, an\n");
printf("\t\tIP address and a default gateway will be assigned automatically\n");
printf("\t\tto the sandbox. The IP address is checked using ARP before\n");
printf("\t\tassignment. The IP address assigned as default gateway is the\n");
printf("\t\tdefault gateway of the host. Up to four --net devices can\n");
printf("\t\tbe defined. Mixing bridge and macvlan devices is allowed.\n\n");
printf("\t--net=none - enable a new, unconnected network namespace.\n\n");
printf("\t--netfilter - enable the default client network filter in the new\n");
printf("\t\tnetwork namespace:\n\n");
printf("\t\t*filter\n");
printf("\t\t:INPUT DROP [0:0]\n");
printf("\t\t:FORWARD DROP [0:0]\n");
printf("\t\t:OUTPUT ACCEPT [0:0]\n");
printf("\t\t-A INPUT -i lo -j ACCEPT\n");
printf("\t\t-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT\n");
printf("\t\t-A INPUT -p icmp --icmp-type destination-unreachable -j ACCEPT\n");
printf("\t\t-A INPUT -p icmp --icmp-type time-exceeded -j ACCEPT\n");
printf("\t\t-A INPUT -p icmp --icmp-type echo-request -j ACCEPT \n");
printf("\t\tCOMMIT\n\n");
printf("\t--netfilter=filename - enable the network filter specified by\n");
printf("\t\tfilename in the new network namespace. The filter file format\n");
printf("\t\tis the format of iptables-save and iptable-restore commands.\n\n");
printf("\t--netstats - monitor network statistics for sandboxes creating a new\n");
printf("\t\tnetwork namespace.\n\n");
printf("\t--nogroups - disable supplementary groups. Without this option,\n");
printf("\t\tsupplementary groups are enabled for the user starting the\n");
printf("\t\tsandbox. For root user supplementary groups are always\n");
printf("\t\tdisabled.\n\n");
printf("\t--noroot - install a user namespace with a single user - the current\n");
printf("\t\tuser. root user does not exist in the new namespace. This option\n");
printf("\t\tis not supported for --chroot and --overlay configurations.\n\n");
printf("\t--output=logfile - stdout logging and log rotation. Copy stdout to\n");
printf("\t\tlogfile, and keep the size of the file under 500KB using log\n");
printf("\t\trotation. Five files with prefixes .1 to .5 are used in\n");
printf("\t\trotation.\n\n");
printf("\t--overlay - mount a filesystem overlay on top of the current filesystem.\n");
printf("\t\t(OverlayFS support is required in Linux kernel for this option\n");
printf("\t\tto work)\n\n");
printf("\t--private - mount new /root and /home/user directories in temporary\n");
printf("\t\tfilesystems. All modifications are discarded when the sandbox is\n");
printf("\t\tclosed.\n\n");
printf("\t--private=directory - use directory as user home.\n\n");
printf("\t--private.keep=file,directory - build a new user home in a temporary\n");
printf("\t\tfilesystem, and copy the files and directories in the list in\n");
printf("\t\tthe new home. All modifications are discarded when the sandbox\n");
printf("\t\tis closed.\n\n");
printf("\t--private-dev - create a new /dev directory. Only null, full, zero, tty,\n");
printf("\t\tpst, ptms, random, urandom and shm devices are available.\n\n");
printf("\t--profile=filename - use a custom profile.\n\n");
printf("\t--read-only=dirname_or_filename - set directory or file read-only.\n\n");
printf("\t--rlimit-fsize=number - set the maximum file size that can be created\n");
printf("\t\tby a process.\n\n");
printf("\t--rlimit-nofile=number - set the maximum number of files that can be\n");
printf("\t\topened by a process.\n\n");
printf("\t--rlimit-nproc=number - set the maximum number of processes that can be\n");
printf("\t\tcreated for the real user ID of the calling process.\n\n");
printf("\t--rlimit-sigpending=number - set the maximum number of pending signals\n");
printf("\t\tfor a process.\n\n");
printf("\t--scan - ARP-scan all the networks from inside a network namespace.\n");
printf("\t\tThis makes it possible to detect macvlan kernel device drivers\n");
printf("\t\trunning on the current host.\n\n");
#ifdef HAVE_SECCOMP
printf("\t--seccomp - enable seccomp filter and blacklist the syscalls in the\n");
printf("\t\tlist. The default list is as follows: mount, umount2,\n");
printf("\t\tptrace, kexec_load, open_by_handle_at, init_module,\n");
printf("\t\tfinit_module, delete_module, iopl, ioperm, swapon, swapoff,\n");
printf("\t\tmknode, syslog, process_vm_readv and process_vm_writev\n");
printf("\t\tsysfs,_sysctl, adjtimex, clock_adjtime, lookup_dcookie,\n");
printf("\t\tperf_event_open, fanotify_init and kcmp.\n\n");
printf("\t--seccomp=syscall,syscall,syscall - enable seccomp filter, blacklist the\n");
printf("\t\tdefault syscall list and the syscalls specified by the command.\n\n");
printf("\t--seccomp.drop=syscall,syscall,syscall - enable seccomp filter, and\n");
printf("\t\tblacklist the syscalls specified by the command.\n\n");
printf("\t--seccomp.keep=syscall,syscall,syscall - enable seccomp filter, and\n");
printf("\t\twhitelist the syscalls specified by the command.\n\n");
printf("\t--seccomp.print=name - print the seccomp filter for the sandbox\n");
printf("\t\tidentified by name.\n\n");
printf("\t--seccomp.print=pid - print the seccomp filter for the sandbox\n");
printf("\t\tidentified by PID.\n\n");
#endif
printf("\t--shell=none - run the program directly without a user shell.\n\n");
printf("\t--shell=program - set default user shell.\n\n");
printf("\t--shutdown=name - shutdown the sandbox identified by name.\n\n");
printf("\t--shutdown=pid - shutdown the sandbox identified by PID.\n\n");
printf("\t--tmpfs=dirname - mount a tmpfs filesystem on directory dirname.\n\n");
printf("\t--top - monitor the most CPU-intensive sandboxes.\n\n");
printf("\t--trace - trace open, access and connect system calls.\n\n");
printf("\t--tree - print a tree of all sandboxed processes.\n\n");
printf("\t--version - print program version and exit.\n\n");
printf("\t--zsh - use /usr/bin/zsh as default shell.\n\n");
printf("\n");
printf("\n");
printf("Traffic Shaping\n\n");
printf("Network bandwidth is an expensive resource shared among all sandboxes\n");
printf("running on a system. Traffic shaping allows the user to increase network\n");
printf("performance by controlling the amount of data that flows into and out of the\n");
printf("sandboxes. Firejail implements a simple rate-limiting shaper based on Linux\n");
printf("command tc. The shaper works at sandbox level, and can be used only for\n");
printf("sandboxes configured with new network namespaces.\n\n");
printf("Set rate-limits:\n");
printf("\tfirejail --bandwidth={name|pid} set network-name down-speed up-speed\n\n");
printf("Clear rate-limits:\n");
printf("\tfirejail --bandwidth={name|pid} clear network-name\n\n");
printf("Status:\n");
printf("\tfirejail --bandwidth={name|pid} status\n\n");
printf("where:\n");
printf("\tname - sandbox name\n");
printf("\tpid - sandbox pid\n");
printf("\tnetwork-name - network name as used by --net option\n");
printf("\tdown-speed - download speed in KB/s (decimal kilobyte per second)\n");
printf("\tup-speed - upload speed in KB/s (decimal kilobyte per second)\n");
printf("\n");
printf("Example:\n");
printf("\t$ firejail --name=mybrowser --net=eth0 firefox &\n");
printf("\t$ firejail --bandwidth=mybrowser set eth0 80 20\n");
printf("\t$ firejail --bandwidth=mybrowser status\n");
printf("\t$ firejail --bandwidth=mybrowser clear eth0\n");
printf("\n");
printf("\n");
printf("Monitoring\n\n");
printf("Option --list prints a list of all sandboxes. The format for each entry is as\n");
printf("follows:\n\n");
printf("\tPID:USER:Command\n\n");
printf("Option --tree prints the tree of processes running in the sandbox. The format\n");
printf("for each process entry is as follows:\n\n");
printf("\tPID:USER:Command\n\n");
printf("Option --top is similar to the UNIX top command, however it applies only to\n");
printf("sandboxes. Listed below are the available fields (columns) in alphabetical\n");
printf("order:\n\n");
printf("\tCommand - command used to start the sandbox.\n");
printf("\tCPU%% - CPU usage, the sandbox share of the elapsed CPU time since the\n");
printf("\t last screen update\n");
printf("\tPID - Unique process ID for the task controlling the sandbox.\n");
printf("\tPrcs - number of processes running in sandbox, including the controlling\n");
printf("\t process.\n");
printf("\tRES - Resident Memory Size (KiB), sandbox non-swapped physical memory.\n");
printf("\t It is a sum of the RES values for all processes running in the\n");
printf("\t sandbox.\n");
printf("\tSHR - Shared Memory Size (KiB), it reflects memory shared with other\n");
printf("\t processes. It is a sum of the SHR values for all processes running\n");
printf("\t in the sandbox, including the controlling process.\n");
printf("\tUptime - sandbox running time in hours:minutes:seconds format.\n");
printf("\tUser - The owner of the sandbox.\n");
printf("\n");
printf("\n");
printf("Profile files\n\n");
printf("Several command line configuration options can be passed to the program using\n");
printf("profile files. Default Firejail profile files are stored in /etc/firejail\n");
printf("directory, user profile files are stored in ~/.config/firejail directory. See\n");
printf("man 5 firejail-profile for more information.\n\n");
printf("\n");
printf("Restricted shell\n\n");
printf("To configure a restricted shell, replace /bin/bash with /usr/bin/firejail i\n");
printf("/etc/password file for each user that needs to be restricted.\n");
printf("Alternatively, you can specify /usr/bin/firejail in adduser command:\n\n");
printf(" adduser --shell /usr/bin/firejail username\n\n");
printf("Arguments to be passed to firejail executable upon login are declared in\n");
printf("/etc/firejail/login.users file.\n\n");
printf("\n");
printf("Examples:\n\n");
printf(" $ firejail\n");
printf(" start a regular /bin/bash session in sandbox\n");
printf(" $ firejail firefox\n");
printf(" start Mozilla Firefox\n");
printf(" $ firejail --seccomp firefox\n");
printf(" start Mozilla Firefox in a seccomp sandbox\n");
printf(" $ firejail --caps firefox\n");
printf(" start Mozilla Firefox in a Linux capabilities sandbox\n");
printf(" $ firejail --debug firefox\n");
printf(" debug Firefox sandbox\n");
printf(" $ firejail --private\n");
printf(" start a /bin/bash session with a new tmpfs home directory\n");
printf(" $ firejail --net=br0 ip=10.10.20.10\n");
printf(" start a /bin/bash session in a new network namespace; the session is\n");
printf(" connected to the main network using br0 bridge device, an IP address\n");
printf(" of 10.10.20.10 is assigned to the sandbox\n");
printf(" $ firejail --net=br0 --net=br1 --net=br2\n");
printf(" start a /bin/bash session in a new network namespace and connect it\n");
printf(" to br0, br1, and br2 host bridge devices\n");
printf(" $ firejail --list\n");
printf(" list all running sandboxes\n");
printf("\n");
printf("License GPL version 2 or later\n");
printf("Homepage: http://firejail.sourceforge.net\n");
printf("\n");
}

480
src/firejail/util.c Normal file
View file

@ -0,0 +1,480 @@
/*
* Copyright (C) 2014, 2015 Firejail Authors
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firejail.h"
#include <sys/stat.h>
#include <fcntl.h>
#include <syslog.h>
#include <errno.h>
#include <dirent.h>
#include <grp.h>
#define MAX_GROUPS 1024
// drop privileges
// - for root group or if nogroups is set, supplementary groups are not configured
void drop_privs(int nogroups) {
gid_t gid = getgid();
// configure supplementary groups
if (gid == 0 || nogroups) {
if (setgroups(0, NULL) < 0)
errExit("setgroups");
if (arg_debug)
printf("Username %s, no supplementary groups\n", cfg.username);
}
else {
assert(cfg.username);
gid_t groups[MAX_GROUPS];
int ngroups = MAX_GROUPS;
int rv = getgrouplist(cfg.username, gid, groups, &ngroups);
if (arg_debug && rv) {
printf("Username %s, groups ", cfg.username);
int i;
for (i = 0; i < ngroups; i++)
printf("%u, ", groups[i]);
printf("\n");
}
if (rv == -1) {
fprintf(stderr, "Warning: cannot extract supplementary group list, dropping them\n");
if (setgroups(0, NULL) < 0)
errExit("setgroups");
}
else {
rv = setgroups(ngroups, groups);
if (rv) {
fprintf(stderr, "Warning: cannot set supplementary group list, dropping them\n");
if (setgroups(0, NULL) < 0)
errExit("setgroups");
}
}
}
// set uid/gid
if (setgid(getgid()) < 0)
errExit("setgid/getgid");
if (setuid(getuid()) < 0)
errExit("setuid/getuid");
}
void logsignal(int s) {
if (!arg_debug)
return;
openlog("firejail", LOG_NDELAY | LOG_PID, LOG_USER);
syslog(LOG_INFO, "Signal %d caught", s);
closelog();
}
void logmsg(const char *msg) {
if (!arg_debug)
return;
openlog("firejail", LOG_NDELAY | LOG_PID, LOG_USER);
syslog(LOG_INFO, "%s\n", msg);
closelog();
}
void logargs(int argc, char **argv) {
if (!arg_debug)
return;
int i;
int len = 0;
// calculate message length
for (i = 0; i < argc; i++)
len += strlen(argv[i]) + 1; // + ' '
// build message
char msg[len + 1];
char *ptr = msg;
for (i = 0; i < argc; i++) {
sprintf(ptr, "%s ", argv[i]);
ptr += strlen(ptr);
}
// log message
logmsg(msg);
}
void logerr(const char *msg) {
if (!arg_debug)
return;
openlog("firejail", LOG_NDELAY | LOG_PID, LOG_USER);
syslog(LOG_ERR, "%s\n", msg);
closelog();
}
// return -1 if error, 0 if no error
int copy_file(const char *srcname, const char *destname) {
assert(srcname);
assert(destname);
// open source
int src = open(srcname, O_RDONLY);
if (src < 0) {
fprintf(stderr, "Warning: cannot open %s, file not copied\n", srcname);
return -1;
}
// open destination
int dst = open(destname, O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (dst < 0) {
fprintf(stderr, "Warning: cannot open %s, file not copied\n", destname);
close(src);
return -1;
}
// copy
ssize_t len;
static const int BUFLEN = 1024;
unsigned char buf[BUFLEN];
while ((len = read(src, buf, BUFLEN)) > 0) {
int done = 0;
while (done != len) {
int rv = write(dst, buf + done, len - done);
if (rv == -1) {
close(src);
close(dst);
return -1;
}
done += rv;
}
}
close(src);
close(dst);
return 0;
}
char *get_link(const char *fname) {
assert(fname);
struct stat sb;
char *linkname;
ssize_t r;
if (lstat(fname, &sb) == -1)
return NULL;
linkname = malloc(sb.st_size + 1);
if (linkname == NULL)
return NULL;
memset(linkname, 0, sb.st_size + 1);
r = readlink(fname, linkname, sb.st_size + 1);
if (r < 0) {
free(linkname);
return NULL;
}
return linkname;
}
// return 1 if the file is a directory
int is_dir(const char *fname) {
assert(fname);
if (*fname == '\0')
return 0;
// if fname doesn't end in '/', add one
int rv;
struct stat s;
if (fname[strlen(fname) - 1] == '/')
rv = stat(fname, &s);
else {
char *tmp;
if (asprintf(&tmp, "%s/", fname) == -1) {
fprintf(stderr, "Error: cannot allocate memory, %s:%d\n", __FILE__, __LINE__);
errExit("asprintf");
}
rv = stat(tmp, &s);
free(tmp);
}
if (rv == -1)
return 0;
if (S_ISDIR(s.st_mode))
return 1;
return 0;
}
// return 1 if the file is a link
int is_link(const char *fname) {
assert(fname);
if (*fname == '\0')
return 0;
struct stat s;
if (lstat(fname, &s) == 0) {
if (S_ISLNK(s.st_mode))
return 1;
}
return 0;
}
// remove multiple spaces and return allocated memory
char *line_remove_spaces(const char *buf) {
assert(buf);
if (strlen(buf) == 0)
return NULL;
// allocate memory for the new string
char *rv = malloc(strlen(buf) + 1);
if (rv == NULL)
errExit("malloc");
// remove space at start of line
const char *ptr1 = buf;
while (*ptr1 == ' ' || *ptr1 == '\t')
ptr1++;
// copy data and remove additional spaces
char *ptr2 = rv;
int state = 0;
while (*ptr1 != '\0') {
if (*ptr1 == '\n' || *ptr1 == '\r')
break;
if (state == 0) {
if (*ptr1 != ' ' && *ptr1 != '\t')
*ptr2++ = *ptr1++;
else {
*ptr2++ = ' ';
ptr1++;
state = 1;
}
}
else { // state == 1
while (*ptr1 == ' ' || *ptr1 == '\t')
ptr1++;
state = 0;
}
}
// strip last blank character if any
if (*(ptr2 - 1) == ' ')
--ptr2;
*ptr2 = '\0';
// if (arg_debug)
// printf("Processing line #%s#\n", rv);
return rv;
}
char *split_comma(char *str) {
if (str == NULL || *str == '\0')
return NULL;
char *ptr = strchr(str, ',');
if (!ptr)
return NULL;
*ptr = '\0';
ptr++;
if (*ptr == '\0')
return NULL;
return ptr;
}
int not_unsigned(const char *str) {
int rv = 0;
const char *ptr = str;
while (*ptr != ' ' && *ptr != '\t' && *ptr != '\0') {
if (!isdigit(*ptr)) {
rv = 1;
break;
}
ptr++;
}
return rv;
}
#define BUFLEN 4096
// find the first child for this parent; return 1 if error
int find_child(pid_t parent, pid_t *child) {
*child = 0; // use it to flag a found child
DIR *dir;
if (!(dir = opendir("/proc"))) {
// sleep 2 seconds and try again
sleep(2);
if (!(dir = opendir("/proc"))) {
fprintf(stderr, "Error: cannot open /proc directory\n");
exit(1);
}
}
struct dirent *entry;
char *end;
while (*child == 0 && (entry = readdir(dir))) {
pid_t pid = strtol(entry->d_name, &end, 10);
if (end == entry->d_name || *end)
continue;
if (pid == parent)
continue;
// open stat file
char *file;
if (asprintf(&file, "/proc/%u/status", pid) == -1) {
perror("asprintf");
exit(1);
}
FILE *fp = fopen(file, "r");
if (!fp) {
free(file);
continue;
}
// look for firejail executable name
char buf[BUFLEN];
while (fgets(buf, BUFLEN - 1, fp)) {
if (strncmp(buf, "PPid:", 5) == 0) {
char *ptr = buf + 5;
while (*ptr != '\0' && (*ptr == ' ' || *ptr == '\t')) {
ptr++;
}
if (*ptr == '\0') {
fprintf(stderr, "Error: cannot read /proc file\n");
exit(1);
}
if (parent == atoi(ptr))
*child = pid;
break; // stop reading the file
}
}
fclose(fp);
free(file);
}
closedir(dir);
return (*child)? 0:1; // 0 = found, 1 = not found
}
void extract_command_name(const char *str) {
assert(str);
cfg.command_name = strdup(str);
if (!cfg.command_name)
errExit("strdup");
// restrict the command name to the first word
char *ptr = cfg.command_name;
while (*ptr != ' ' && *ptr != '\t' && *ptr != '\0')
ptr++;
*ptr = '\0';
// remove the path: /usr/bin/firefox becomes firefox
ptr = strrchr(cfg.command_name, '/');
if (ptr) {
ptr++;
if (*ptr == '\0') {
fprintf(stderr, "Error: invalid command name\n");
exit(1);
}
char *tmp = strdup(ptr);
if (!tmp)
errExit("strdup");
free(cfg.command_name);
cfg.command_name = tmp;
}
}
void update_map(char *mapping, char *map_file) {
int fd, j;
size_t map_len; /* Length of 'mapping' */
/* Replace commas in mapping string with newlines */
map_len = strlen(mapping);
for (j = 0; j < map_len; j++)
if (mapping[j] == ',')
mapping[j] = '\n';
fd = open(map_file, O_RDWR);
if (fd == -1) {
fprintf(stderr, "Error: cannot open %s: %s\n", map_file, strerror(errno));
exit(EXIT_FAILURE);
}
if (write(fd, mapping, map_len) != map_len) {
fprintf(stderr, "Error: cannot write to %s: %s\n", map_file, strerror(errno));
exit(EXIT_FAILURE);
}
close(fd);
}
void wait_for_other(int fd) {
//****************************
// wait for the parent to be initialized
//****************************
char childstr[BUFLEN + 1];
int newfd = dup(fd);
if (newfd == -1)
errExit("dup");
FILE* stream;
stream = fdopen(newfd, "r");
*childstr = '\0';
if (fgets(childstr, BUFLEN, stream)) {
// remove \n)
char *ptr = childstr;
while(*ptr !='\0' && *ptr != '\n')
ptr++;
if (*ptr == '\0')
errExit("fgets");
*ptr = '\0';
}
else {
fprintf(stderr, "Error: cannot establish communication with the parent, exiting...\n");
exit(1);
}
fclose(stream);
}
void notify_other(int fd) {
FILE* stream;
int newfd = dup(fd);
if (newfd == -1)
errExit("dup");
stream = fdopen(newfd, "w");
fprintf(stream, "%u\n", getpid());
fflush(stream);
fclose(stream);
}

191
src/firejail/veth.c Normal file
View file

@ -0,0 +1,191 @@
/* code based on iproute2 ip/iplink.c, modified to be included in firejail project
*
* Original source code:
*
* Information:
* http://www.linuxfoundation.org/collaborate/workgroups/networking/iproute2
*
* Download:
* http://www.kernel.org/pub/linux/utils/net/iproute2/
*
* Repository:
* git://git.kernel.org/pub/scm/linux/kernel/git/shemminger/iproute2.git
*
* License: GPL v2
*
* Original copyright header
*
* iplink.c "ip link".
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
* Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
*
*/
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firejail.h"
#include "../include/libnetlink.h"
#include <linux/veth.h>
struct iplink_req
{
struct nlmsghdr n;
struct ifinfomsg i;
char buf[1024];
};
static struct rtnl_handle rth = { .fd = -1 };
int net_create_veth(const char *dev, const char *nsdev, unsigned pid) {
int len;
struct iplink_req req;
if (arg_debug)
printf("create veth %s/%s/%u\n", dev, nsdev, pid);
assert(dev);
assert(nsdev);
assert(pid);
if (rtnl_open(&rth, 0) < 0) {
fprintf(stderr, "cannot open netlink\n");
exit(1);
}
memset(&req, 0, sizeof(req));
req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
req.n.nlmsg_flags = NLM_F_REQUEST|NLM_F_CREATE|NLM_F_EXCL;
req.n.nlmsg_type = RTM_NEWLINK;
req.i.ifi_family = 0;
if (dev) {
len = strlen(dev) + 1;
addattr_l(&req.n, sizeof(req), IFLA_IFNAME, dev, len);
}
struct rtattr *linkinfo = NLMSG_TAIL(&req.n);
addattr_l(&req.n, sizeof(req), IFLA_LINKINFO, NULL, 0);
addattr_l(&req.n, sizeof(req), IFLA_INFO_KIND, "veth", strlen("veth"));
struct rtattr * data = NLMSG_TAIL(&req.n);
addattr_l(&req.n, sizeof(req), IFLA_INFO_DATA, NULL, 0);
struct rtattr * peerdata = NLMSG_TAIL(&req.n);
addattr_l (&req.n, sizeof(req), VETH_INFO_PEER, NULL, 0);
req.n.nlmsg_len += sizeof(struct ifinfomsg);
// place the link in the child namespace
addattr_l (&req.n, sizeof(req), IFLA_NET_NS_PID, &pid, 4);
if (nsdev) {
int len = strlen(nsdev) + 1;
addattr_l(&req.n, sizeof(req), IFLA_IFNAME, nsdev, len);
}
peerdata->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)peerdata;
data->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)data;
linkinfo->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)linkinfo;
// send message
if (rtnl_talk(&rth, &req.n, 0, 0, NULL) < 0)
exit(2);
return 0;
}
int net_create_macvlan(const char *dev, const char *parent, unsigned pid) {
int len;
struct iplink_req req;
if (arg_debug)
printf("create macvlan %s, parent %s\n", dev, parent);
assert(dev);
assert(parent);
if (rtnl_open(&rth, 0) < 0) {
fprintf(stderr, "cannot open netlink\n");
exit(1);
}
memset(&req, 0, sizeof(req));
req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
req.n.nlmsg_flags = NLM_F_REQUEST|NLM_F_CREATE|NLM_F_EXCL;
req.n.nlmsg_type = RTM_NEWLINK;
req.i.ifi_family = 0;
// we start with the parent
int parent_ifindex = 2;
addattr_l(&req.n, sizeof(req), IFLA_LINK, &parent_ifindex, 4);
// add new interface name
len = strlen(dev) + 1;
addattr_l(&req.n, sizeof(req), IFLA_IFNAME, dev, len);
// place the interface in child namespace
addattr_l (&req.n, sizeof(req), IFLA_NET_NS_PID, &pid, 4);
// add link info for the new interface
struct rtattr *linkinfo = NLMSG_TAIL(&req.n);
addattr_l(&req.n, sizeof(req), IFLA_LINKINFO, NULL, 0);
addattr_l(&req.n, sizeof(req), IFLA_INFO_KIND, "macvlan", strlen("macvlan"));
// set macvlan bridge mode
struct rtattr * data = NLMSG_TAIL(&req.n);
addattr_l(&req.n, sizeof(req), IFLA_INFO_DATA, NULL, 0);
int macvlan_type = MACVLAN_MODE_BRIDGE;
addattr_l (&req.n, sizeof(req), IFLA_INFO_KIND, &macvlan_type, 4);
data->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)data;
// req.n.nlmsg_len += sizeof(struct ifinfomsg);
data->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)data;
linkinfo->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)linkinfo;
// send message
if (rtnl_talk(&rth, &req.n, 0, 0, NULL) < 0)
exit(2);
return 0;
}
/*
int main(int argc, char **argv) {
printf("Hello\n");
char *dev = argv[3];
char *nsdev = argv[8];
unsigned pid;
sscanf(argv[10], "%u", &pid);
net_create_veth(dev, nsdev, pid);
return 0;
}
*/

24
src/firemon/Makefile.in Normal file
View file

@ -0,0 +1,24 @@
all: firemon
PREFIX=@prefix@
VERSION=@PACKAGE_VERSION@
NAME=@PACKAGE_NAME@
H_FILE_LIST = $(wildcard *.[h])
C_FILE_LIST = $(wildcard *.c)
OBJS = $(C_FILE_LIST:.c=.o)
BINOBJS = $(foreach file, $(OBJS), $file)
CFLAGS += -ggdb -O2 -DVERSION='"$(VERSION)"' -fstack-protector-all -D_FORTIFY_SOURCE=2 -fPIE -pie -Wformat -Wformat-security
LDFLAGS += -pie -Wl,-z,relro -Wl,-z,now
%.o : %.c $(H_FILE_LIST)
$(CC) $(CFLAGS) $(INCLUDE) -c $< -o $@
firemon: $(OBJS) ../lib/common.o ../lib/pid.o
$(CC) $(LDFLAGS) -o $@ $(OBJS) ../lib/common.o ../lib/pid.o $(LIBS)
clean:; rm -f *.o firemon
distclean: clean
rm -fr Makefile

99
src/firemon/arp.c Normal file
View file

@ -0,0 +1,99 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firemon.h"
#define MAXBUF 4096
static void print_arp(const char *fname) {
FILE *fp = fopen(fname, "r");
if (!fp)
return;
printf(" ARP Table:\n");
char buf[MAXBUF];
while (fgets(buf, MAXBUF, fp)) {
// remove blanks, \n
char *ptr = buf;
while (*ptr == ' ' || *ptr == '\t')
ptr++;
char *start = ptr;
if (*start == '\0')
continue;
ptr = strchr(ptr, '\n');
if (ptr)
*ptr = '\0';
// remove table header
//IP address HW type Flags HW address Mask Device
if (strncmp(start, "IP address", 10) == 0)
continue;
// extract data
char ip[64];
char type[64];
char flags[64];
char mac[64];
char mask[64];
char device[64];
int rv = sscanf(start, "%s %s %s %s %s %s\n", ip, type, flags, mac, mask, device);
if (rv != 6)
continue;
// destination ip
unsigned a, b, c, d;
if (sscanf(ip, "%u.%u.%u.%u", &a, &b, &c, &d) != 4 || a > 255 || b > 255 || c > 255 || d > 255)
continue;
uint32_t destip = a * 0x1000000 + b * 0x10000 + c * 0x100 + d;
if (strcmp(flags, "0x0") == 0)
printf(" %d.%d.%d.%d dev %s FAILED\n",
PRINT_IP(destip), device);
else
printf(" %d.%d.%d.%d dev %s lladdr %s REACHABLE\n",
PRINT_IP(destip), device, mac);
}
fclose(fp);
}
void arp(pid_t pid) {
if (getuid() == 0)
firemon_drop_privs();
pid_read(pid);
// print processes
int i;
for (i = 0; i < max_pids; i++) {
if (pids[i].level == 1) {
pid_print_list(i, 0);
int child = find_child(i);
if (child != -1) {
char *fname;
if (asprintf(&fname, "/proc/%d/net/arp", child) == -1)
errExit("asprintf");
print_arp(fname);
free(fname);
printf("\n");
}
}
}
}

69
src/firemon/caps.c Normal file
View file

@ -0,0 +1,69 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firemon.h"
#define MAXBUF 4098
static void print_caps(int pid) {
char *file;
if (asprintf(&file, "/proc/%d/status", pid) == -1) {
errExit("asprintf");
exit(1);
}
FILE *fp = fopen(file, "r");
if (!fp) {
printf(" Error: cannot open %s\n", file);
free(file);
return;
}
char buf[MAXBUF];
while (fgets(buf, MAXBUF, fp)) {
if (strncmp(buf, "CapBnd:", 7) == 0) {
printf(" %s", buf);
fflush(0);
free(file);
fclose(fp);
return;
}
}
fclose(fp);
free(file);
}
void caps(pid_t pid) {
if (getuid() == 0)
firemon_drop_privs();
pid_read(pid); // include all processes
// print processes
int i;
for (i = 0; i < max_pids; i++) {
if (pids[i].level == 1) {
pid_print_list(i, 0);
int child = find_child(i);
if (child != -1)
print_caps(child);
}
}
printf("\n");
}

64
src/firemon/cgroup.c Normal file
View file

@ -0,0 +1,64 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firemon.h"
#define MAXBUF 4098
static void print_cgroup(int pid) {
char *file;
if (asprintf(&file, "/proc/%d/cgroup", pid) == -1) {
errExit("asprintf");
exit(1);
}
FILE *fp = fopen(file, "r");
if (!fp) {
printf(" Error: cannot open %s\n", file);
free(file);
return;
}
char buf[MAXBUF];
if (fgets(buf, MAXBUF, fp)) {
printf(" %s", buf);
fflush(0);
}
fclose(fp);
free(file);
}
void cgroup(pid_t pid) {
if (getuid() == 0)
firemon_drop_privs();
pid_read(pid);
// print processes
int i;
for (i = 0; i < max_pids; i++) {
if (pids[i].level == 1) {
pid_print_list(i, 0);
int child = find_child(i);
if (child != -1)
print_cgroup(child);
}
}
}

68
src/firemon/cpu.c Normal file
View file

@ -0,0 +1,68 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firemon.h"
#define MAXBUF 4098
static void print_cpu(int pid) {
char *file;
if (asprintf(&file, "/proc/%d/status", pid) == -1) {
errExit("asprintf");
exit(1);
}
FILE *fp = fopen(file, "r");
if (!fp) {
printf(" Error: cannot open %s\n", file);
free(file);
return;
}
char buf[MAXBUF];
while (fgets(buf, MAXBUF, fp)) {
if (strncmp(buf, "Cpus_allowed_list:", 18) == 0) {
printf(" %s", buf);
fflush(0);
free(file);
fclose(fp);
return;
}
}
fclose(fp);
free(file);
}
void cpu(pid_t pid) {
if (getuid() == 0)
firemon_drop_privs();
pid_read(pid);
// print processes
int i;
for (i = 0; i < max_pids; i++) {
if (pids[i].level == 1) {
pid_print_list(i, 0);
int child = find_child(i);
if (child != -1)
print_cpu(child);
}
}
}

222
src/firemon/firemon.c Normal file
View file

@ -0,0 +1,222 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firemon.h"
#include <signal.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <grp.h>
static int arg_route = 0;
static int arg_arp = 0;
static int arg_tree = 0;
static int arg_interface = 0;
static int arg_seccomp = 0;
static int arg_caps = 0;
static int arg_cpu = 0;
static int arg_cgroup = 0;
int arg_nowrap = 0;
static struct termios tlocal; // startup terminal setting
static struct termios twait; // no wait on key press
static int terminal_set = 0;
static void my_handler(int s){
if (terminal_set)
tcsetattr(0, TCSANOW, &tlocal);
exit(0);
}
// find the first child process for the specified pid
// return -1 if not found
int find_child(int id) {
int i;
for (i = 0; i < max_pids; i++) {
if (pids[i].level == 2 && pids[i].parent == id)
return i;
}
return -1;
}
// drop privileges
void firemon_drop_privs(void) {
// drop privileges
if (setgroups(0, NULL) < 0)
errExit("setgroups");
if (setgid(getgid()) < 0)
errExit("setgid/getgid");
if (setuid(getuid()) < 0)
errExit("setuid/getuid");
}
// sleep and wait for a key to be pressed
void firemon_sleep(int st) {
if (terminal_set == 0) {
tcgetattr(0, &twait); // get current terminal attirbutes; 0 is the file descriptor for stdin
memcpy(&tlocal, &twait, sizeof(tlocal));
twait.c_lflag &= ~ICANON; // disable canonical mode
twait.c_lflag &= ~ECHO; // no echo
twait.c_cc[VMIN] = 1; // wait until at least one keystroke available
twait.c_cc[VTIME] = 0; // no timeout
terminal_set = 1;
}
tcsetattr(0, TCSANOW, &twait);
fd_set fds;
FD_ZERO(&fds);
FD_SET(0,&fds);
int maxfd = 1;
struct timeval ts;
ts.tv_sec = st;
ts.tv_usec = 0;
int ready = select(maxfd, &fds, (fd_set *) 0, (fd_set *) 0, &ts);
(void) ready;
if( FD_ISSET(0, &fds)) {
getchar();
tcsetattr(0, TCSANOW, &tlocal);
printf("\n");
exit(0);
}
tcsetattr(0, TCSANOW, &tlocal);
}
int main(int argc, char **argv) {
unsigned pid = 0;
int i;
// handle CTRL-C
signal (SIGINT, my_handler);
signal (SIGTERM, my_handler);
for (i = 1; i < argc; i++) {
// default options
if (strcmp(argv[i], "--help") == 0 ||
strcmp(argv[i], "-?") == 0) {
usage();
return 0;
}
else if (strcmp(argv[i], "--version") == 0) {
printf("firemon version %s\n\n", VERSION);
return 0;
}
// options without a pid argument
else if (strcmp(argv[i], "--top") == 0) {
top(); // never to return
}
else if (strcmp(argv[i], "--list") == 0) {
list();
return 0;
}
else if (strcmp(argv[i], "--netstats") == 0) {
netstats();
return 0;
}
// cumulative options with or without a pid argument
else if (strcmp(argv[i], "--cgroup") == 0) {
arg_cgroup = 1;
}
else if (strcmp(argv[i], "--cpu") == 0) {
arg_cpu = 1;
}
else if (strcmp(argv[i], "--seccomp") == 0) {
arg_seccomp = 1;
}
else if (strcmp(argv[i], "--caps") == 0) {
arg_caps = 1;
}
else if (strcmp(argv[i], "--tree") == 0) {
arg_tree = 1;
}
else if (strcmp(argv[i], "--interface") == 0) {
arg_interface = 1;
}
else if (strcmp(argv[i], "--route") == 0) {
arg_route = 1;
}
else if (strcmp(argv[i], "--arp") == 0) {
arg_arp = 1;
}
else if (strncmp(argv[i], "--name=", 7) == 0) {
char *name = argv[i] + 7;
if (name2pid(name, (pid_t *) &pid)) {
fprintf(stderr, "Error: cannot find sandbox %s\n", name);
return 1;
}
}
// etc
else if (strcmp(argv[i], "--nowrap") == 0)
arg_nowrap = 1;
// invalid option
else if (*argv[i] == '-') {
fprintf(stderr, "Error: invalid option\n");
return 1;
}
// PID argument
else {
// this should be a pid number
char *ptr = argv[i];
while (*ptr != '\0') {
if (!isdigit(*ptr)) {
fprintf(stderr, "Error: not a valid PID number\n");
exit(1);
}
ptr++;
}
sscanf(argv[i], "%u", &pid);
break;
}
}
if (arg_tree)
tree((pid_t) pid);
if (arg_interface)
interface((pid_t) pid);
if (arg_route)
route((pid_t) pid);
if (arg_arp)
arp((pid_t) pid);
if (arg_seccomp)
seccomp((pid_t) pid);
if (arg_caps)
caps((pid_t) pid);
if (arg_cpu)
cpu((pid_t) pid);
if (arg_cgroup)
cgroup((pid_t) pid);
if (!arg_route && !arg_arp && !arg_interface && !arg_tree && !arg_caps && !arg_seccomp)
procevent((pid_t) pid); // never to return
return 0;
}

84
src/firemon/firemon.h Normal file
View file

@ -0,0 +1,84 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef FIREMON_H
#define FIREMON_H
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>
#include <stdint.h>
#include "../include/pid.h"
#include "../include/common.h"
// clear screen
static inline void firemon_clrscr(void) {
printf("\033[2J\033[1;1H");
fflush(0);
}
// firemon.c
extern int arg_nowrap;
int find_child(int id);
void firemon_drop_privs(void);
void firemon_sleep(int st);
// procevent.c
void procevent(pid_t pid);
// usage.c
void usage(void);
// top.c
void top(void);
// list.c
void list(void);
// interface.c
void interface(pid_t pid);
// arp.c
void arp(pid_t pid);
// route.c
void route(pid_t pid);
// caps.c
void caps(pid_t pid);
// seccomp.c
void seccomp(pid_t pid);
// cpu.c
void cpu(pid_t pid);
// cgroup.c
void cgroup(pid_t pid);
// tree.c
void tree(pid_t pid);
// netstats.c
void netstats(void);
#endif

176
src/firemon/interface.c Normal file
View file

@ -0,0 +1,176 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firemon.h"
#include <sys/types.h>
#include <sys/wait.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <linux/connector.h>
#include <linux/netlink.h>
#include <linux/if_link.h>
#include <linux/sockios.h>
#include <sys/ioctl.h>
//#include <net/route.h>
//#include <linux/if_bridge.h>
// print IP addresses for all interfaces
static void net_ifprint(void) {
uint32_t ip;
uint32_t mask;
struct ifaddrs *ifaddr, *ifa;
int fd;
if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
fprintf(stderr, "Error: cannot open AF_INET socket\n");
exit(1);
}
if (getifaddrs(&ifaddr) == -1)
errExit("getifaddrs");
// walk through the linked list
printf(" Link status:\n");
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == NULL)
continue;
if (ifa->ifa_addr->sa_family == AF_PACKET) {
if (ifa->ifa_flags & IFF_RUNNING && ifa->ifa_flags & IFF_UP) {
if (ifa->ifa_data != NULL) {
struct rtnl_link_stats *stats = ifa->ifa_data;
// extract mac address
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, ifa->ifa_name, IFNAMSIZ);
int rv = ioctl (fd, SIOCGIFHWADDR, &ifr);
if (rv == 0)
printf(" %s UP, %02x:%02x:%02x:%02x:%02x:%02x\n",
ifa->ifa_name, PRINT_MAC((unsigned char *) &ifr.ifr_hwaddr.sa_data));
else
printf(" %s UP\n", ifa->ifa_name);
printf(" tx/rx: %u/%u packets, %u/%u bytes\n",
stats->tx_packets, stats->rx_packets,
stats->tx_bytes, stats->rx_bytes);
}
}
else
printf(" %s DOWN\n", ifa->ifa_name);
}
}
// walk through the linked list
printf(" IPv4 status:\n");
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == NULL)
continue;
if (ifa->ifa_addr->sa_family == AF_INET) {
struct sockaddr_in *si = (struct sockaddr_in *) ifa->ifa_netmask;
mask = ntohl(si->sin_addr.s_addr);
si = (struct sockaddr_in *) ifa->ifa_addr;
ip = ntohl(si->sin_addr.s_addr);
char *status;
if (ifa->ifa_flags & IFF_RUNNING && ifa->ifa_flags & IFF_UP)
status = "UP";
else
status = "DOWN";
printf(" %s %s, %d.%d.%d.%d/%u\n",
ifa->ifa_name, status, PRINT_IP(ip), mask2bits(mask));
}
}
// walk through the linked list
printf(" IPv6 status:\n");
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == NULL)
continue;
if (ifa->ifa_addr->sa_family == AF_INET6) {
char host[NI_MAXHOST];
int s = getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in6),
host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
if (s == 0) {
char *ptr;
if ((ptr = strchr(host, '%')) != NULL)
*ptr = '\0';
char *status;
if (ifa->ifa_flags & IFF_RUNNING && ifa->ifa_flags & IFF_UP)
status = "UP";
else
status = "DOWN";
printf(" %s %s, %s\n", ifa->ifa_name, status, host);
}
}
}
freeifaddrs(ifaddr);
close(fd);
}
static void print_sandbox(pid_t pid) {
pid_t child = fork();
if (child == -1)
return;
if (child == 0) {
int rv = join_namespace(pid, "net");
if (rv)
return;
net_ifprint();
printf("\n");
exit(0);
}
// wait for the child to finish
waitpid(child, NULL, 0);
}
void interface(pid_t pid) {
if (getuid() != 0) {
fprintf(stderr, "Error: you need to be root to run this command\n");
exit(1);
}
pid_read(pid); // a pid of 0 will include all processes
// print processes
int i;
for (i = 0; i < max_pids; i++) {
if (pids[i].level == 1) {
pid_print_list(i, 0);
int child = find_child(i);
if (child != -1) {
print_sandbox(child);
}
}
}
}

35
src/firemon/list.c Normal file
View file

@ -0,0 +1,35 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firemon.h"
void list(void) {
if (getuid() == 0)
firemon_drop_privs();
pid_read(0); // include all processes
// print processes
int i;
for (i = 0; i < max_pids; i++) {
if (pids[i].level == 1)
pid_print_list(i, 0);
}
}

214
src/firemon/netstats.c Normal file
View file

@ -0,0 +1,214 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firemon.h"
#include <termios.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#define MAXBUF 4096
static char *get_header(void) {
char *rv;
if (asprintf(&rv, "%-5.5s %-9.9s %-10.10s %-10.10s %s",
"PID", "User", "RX(KB/s)", "TX(KB/s)", "Command") == -1)
errExit("asprintf");
return rv;
}
void get_stats(int parent) {
// find the first child
int child = -1;
for (child = parent + 1; child < max_pids; child++) {
if (pids[child].parent == parent)
break;
}
if (child == -1)
goto errexit;
// open /proc/child/net/dev file and read rx and tx
char *fname;
if (asprintf(&fname, "/proc/%d/net/dev", child) == -1)
errExit("asprintf");
FILE *fp = fopen(fname, "r");
if (!fp) {
free(fname);
goto errexit;
}
char buf[MAXBUF];
long long unsigned rx = 0;
long long unsigned tx = 0;
while (fgets(buf, MAXBUF, fp)) {
if (strncmp(buf, "Inter", 5) == 0)
continue;
if (strncmp(buf, " face", 5) == 0)
continue;
char *ptr = buf;
while (*ptr != '\0' && *ptr != ':') {
ptr++;
}
if (*ptr == '\0') {
fclose(fp);
free(fname);
goto errexit;
}
ptr++;
long long unsigned rxval;
long long unsigned txval;
unsigned a, b, c, d, e, f, g;
sscanf(ptr, "%llu %u %u %u %u %u %u %u %llu",
&rxval, &a, &b, &c, &d, &e, &f, &g, &txval);
rx += rxval;
tx += txval;
}
// store data
pids[parent].rx_delta = rx - pids[parent].rx;
pids[parent].rx = rx;
pids[parent].tx_delta = tx - pids[parent].tx;
pids[parent].tx = tx;
free(fname);
fclose(fp);
return;
errexit:
pids[parent].rx = 0;
pids[parent].tx = 0;
pids[parent].rx_delta = 0;
pids[parent].tx_delta = 0;
}
static void print_proc(int index, int itv, int col) {
// command
char *cmd = pid_proc_cmdline(index);
char *ptrcmd;
if (cmd == NULL) {
if (pids[index].zombie)
ptrcmd = "(zombie)";
else
ptrcmd = "";
}
else
ptrcmd = cmd;
// if the command doesn't have a --net= option, don't print
if (strstr(ptrcmd, "--net=") == NULL) {
if (cmd)
free(cmd);
return;
}
// pid
char pidstr[10];
snprintf(pidstr, 10, "%u", index);
// user
char *user = pid_get_user_name(pids[index].uid);
char *ptruser;
if (user)
ptruser = user;
else
ptruser = "";
float rx_kbps = ((float) pids[index].rx_delta / 1000) / itv;
char ptrrx[15];
sprintf(ptrrx, "%.03f", rx_kbps);
float tx_kbps = ((float) pids[index].tx_delta / 1000) / itv;
char ptrtx[15];
sprintf(ptrtx, "%.03f", tx_kbps);
char buf[1024 + 1];
snprintf(buf, 1024, "%-5.5s %-9.9s %-10.10s %-10.10s %s",
pidstr, ptruser, ptrrx, ptrtx, ptrcmd);
if (col < 1024)
buf[col] = '\0';
printf("%s\n", buf);
if (cmd)
free(cmd);
if (user)
free(user);
}
void netstats(void) {
if (getuid() == 0)
firemon_drop_privs();
pid_read(0); // include all processes
printf("Displaying network statistics only for sandboxes using a new network namespace.\n");
// print processes
while (1) {
// set pid table
int i;
int itv = 5; // 5 second interval
pid_read(0); // todo: preserve the last calculation if any, so we don't have to do get_stats()
// start rx/tx measurements
for (i = 0; i < max_pids; i++) {
if (pids[i].level == 1)
get_stats(i);
}
// wait 5 seconds
firemon_sleep(itv);
// grab screen size
struct winsize sz;
int row = 24;
int col = 80;
if (!ioctl(0, TIOCGWINSZ, &sz)) {
col = sz.ws_col;
row = sz.ws_row;
}
// start printing
firemon_clrscr();
char *header = get_header();
if (strlen(header) > col)
header[col] = '\0';
printf("%s\n", header);
if (row > 0)
row--;
free(header);
// start rx/tx measurements
for (i = 0; i < max_pids; i++) {
if (pids[i].level == 1) {
get_stats(i);
print_proc(i, itv, col);
}
}
}
}

377
src/firemon/procevent.c Normal file
View file

@ -0,0 +1,377 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firemon.h"
#include <sys/socket.h>
#include <linux/connector.h>
#include <linux/netlink.h>
#include <linux/cn_proc.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <time.h>
#define PIDS_BUFLEN 4096
#define SERVER_PORT 889 // 889-899 is left unassigned by IANA
static int pid_is_firejail(pid_t pid) {
uid_t rv = 0;
// open stat file
char *file;
if (asprintf(&file, "/proc/%u/status", pid) == -1) {
perror("asprintf");
exit(1);
}
FILE *fp = fopen(file, "r");
if (!fp) {
free(file);
return 0;
}
// look for firejail executable name
char buf[PIDS_BUFLEN];
while (fgets(buf, PIDS_BUFLEN - 1, fp)) {
if (strncmp(buf, "Name:", 5) == 0) {
char *ptr = buf + 5;
while (*ptr != '\0' && (*ptr == ' ' || *ptr == '\t')) {
ptr++;
}
if (*ptr == '\0')
goto doexit;
if (strncmp(ptr, "firejail", 8) == 0)
rv = 1;
// if (strncmp(ptr, "lxc-execute", 11) == 0)
// rv = 1;
break;
}
}
doexit:
fclose(fp);
free(file);
return rv;
}
static int procevent_netlink_setup(void) {
// open socket for process event connector
int sock;
if ((sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR)) < 0) {
fprintf(stderr, "Error: cannot open netlink socket\n");
exit(1);
}
// bind socket
struct sockaddr_nl addr;
memset(&addr, 0, sizeof(addr));
addr.nl_pid = getpid();
addr.nl_family = AF_NETLINK;
addr.nl_groups = CN_IDX_PROC;
if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
fprintf(stderr, "Error: cannot bind to netlink socket\n");
exit(1);
}
// send monitoring message
struct nlmsghdr nlmsghdr;
memset(&nlmsghdr, 0, sizeof(nlmsghdr));
nlmsghdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct cn_msg) + sizeof(enum proc_cn_mcast_op));
nlmsghdr.nlmsg_pid = getpid();
nlmsghdr.nlmsg_type = NLMSG_DONE;
struct cn_msg cn_msg;
memset(&cn_msg, 0, sizeof(cn_msg));
cn_msg.id.idx = CN_IDX_PROC;
cn_msg.id.val = CN_VAL_PROC;
cn_msg.len = sizeof(enum proc_cn_mcast_op);
struct iovec iov[3];
iov[0].iov_base = &nlmsghdr;
iov[0].iov_len = sizeof(nlmsghdr);
iov[1].iov_base = &cn_msg;
iov[1].iov_len = sizeof(cn_msg);
enum proc_cn_mcast_op op = PROC_CN_MCAST_LISTEN;
iov[2].iov_base = &op;
iov[2].iov_len = sizeof(op);
if (writev(sock, iov, 3) == -1) {
fprintf(stderr, "Error: cannot write to netlink socket\n");
exit(1);
}
return sock;
}
static int procevent_monitor(const int sock, pid_t mypid) {
ssize_t len;
struct nlmsghdr *nlmsghdr;
// timeout in order to re-enable firejail module trace
struct timeval tv;
tv.tv_sec = 30;
tv.tv_usec = 0;
while (1) {
#define BUFFSIZE 4096
char __attribute__ ((aligned(NLMSG_ALIGNTO)))buf[BUFFSIZE];
fd_set readfds;
int max;
FD_ZERO(&readfds);
FD_SET(sock, &readfds);
max = sock;
max++;
int rv = select(max, &readfds, NULL, NULL, &tv);
if (rv == -1) {
fprintf(stderr, "recv: %s\n", strerror(errno));
return -1;
}
// timeout
if (rv == 0) {
tv.tv_sec = 30;
tv.tv_usec = 0;
continue;
}
if ((len = recv(sock, buf, sizeof(buf), 0)) == 0) {
return 0;
}
if (len == -1) {
if (errno == EINTR) {
return 0;
} else {
fprintf(stderr,"recv: %s\n", strerror(errno));
return -1;
}
}
for (nlmsghdr = (struct nlmsghdr *)buf;
NLMSG_OK (nlmsghdr, len);
nlmsghdr = NLMSG_NEXT (nlmsghdr, len)) {
struct cn_msg *cn_msg;
struct proc_event *proc_ev;
struct tm tm;
time_t now;
if ((nlmsghdr->nlmsg_type == NLMSG_ERROR) ||
(nlmsghdr->nlmsg_type == NLMSG_NOOP))
continue;
cn_msg = NLMSG_DATA(nlmsghdr);
if ((cn_msg->id.idx != CN_IDX_PROC) ||
(cn_msg->id.val != CN_VAL_PROC))
continue;
(void)time(&now);
(void)localtime_r(&now, &tm);
char line[PIDS_BUFLEN];
char *lineptr = line;
sprintf(lineptr, "%2.2d:%2.2d:%2.2d", tm.tm_hour, tm.tm_min, tm.tm_sec);
lineptr += strlen(lineptr);
proc_ev = (struct proc_event *)cn_msg->data;
pid_t pid = 0;
pid_t child = 0;
int remove_pid = 0;
switch (proc_ev->what) {
case PROC_EVENT_FORK:
if (proc_ev->event_data.fork.child_pid !=
proc_ev->event_data.fork.child_tgid)
continue; // this is a thread, not a process
pid = proc_ev->event_data.fork.parent_tgid;
if (pids[pid].level > 0) {
child = proc_ev->event_data.fork.child_tgid;
child %= max_pids;
pids[child].level = pids[pid].level + 1;
pids[child].uid = pid_get_uid(child);
}
sprintf(lineptr, " fork");
break;
case PROC_EVENT_EXEC:
pid = proc_ev->event_data.exec.process_tgid;
sprintf(lineptr, " exec");
break;
case PROC_EVENT_EXIT:
if (proc_ev->event_data.exit.process_pid !=
proc_ev->event_data.exit.process_tgid)
continue; // this is a thread, not a process
pid = proc_ev->event_data.exit.process_tgid;
remove_pid = 1;
sprintf(lineptr, " exit");
break;
case PROC_EVENT_UID:
pid = proc_ev->event_data.id.process_tgid;
sprintf(lineptr, " uid ");
break;
case PROC_EVENT_GID:
pid = proc_ev->event_data.id.process_tgid;
sprintf(lineptr, " gid ");
break;
case PROC_EVENT_SID:
pid = proc_ev->event_data.sid.process_tgid;
sprintf(lineptr, " sid ");
break;
default:
sprintf(lineptr, "\n");
continue;
}
int add_new = 0;
if (pids[pid].level < 0) // not a firejail process
continue;
else if (pids[pid].level == 0) { // new porcess, do we track it?
if (pid_is_firejail(pid) && mypid == 0) {
pids[pid].level = 1;
add_new = 1;
}
else {
pids[pid].level = -1;
continue;
}
}
lineptr += strlen(lineptr);
sprintf(lineptr, " %u", pid);
lineptr += strlen(lineptr);
char *user = pids[pid].user;
if (!user)
user = pid_get_user_name(pids[pid].uid);
if (user) {
pids[pid].user = user;
sprintf(lineptr, " (%s)", user);
lineptr += strlen(lineptr);
}
int sandbox_closed = 0; // exit sandbox flag
char *cmd = pids[pid].cmd;
if (!cmd) {
cmd = pid_proc_cmdline(pid);
}
if (add_new) {
if (!cmd)
sprintf(lineptr, " NEW SANDBOX\n");
else
sprintf(lineptr, " NEW SANDBOX: %s\n", cmd);
lineptr += strlen(lineptr);
}
else if (proc_ev->what == PROC_EVENT_EXIT && pids[pid].level == 1) {
sprintf(lineptr, " EXIT SANDBOX\n");
lineptr += strlen(lineptr);
if (mypid == pid)
sandbox_closed = 1;
}
else {
if (!cmd) {
cmd = pid_proc_cmdline(pid);
}
if (cmd == NULL)
sprintf(lineptr, "\n");
else {
sprintf(lineptr, " %s\n", cmd);
free(cmd);
}
lineptr += strlen(lineptr);
}
(void) lineptr;
// print the event
printf("%s", line);
fflush(0);
// unflag pid for exit events
if (remove_pid) {
if (pids[pid].user)
free(pids[pid].user);
if (pids[pid].cmd)
free(pids[pid].cmd);
memset(&pids[pid], 0, sizeof(Process));
}
// print forked child
if (child) {
cmd = pid_proc_cmdline(child);
if (cmd) {
printf("\tchild %u %s\n", child, cmd);
free(cmd);
}
else
printf("\tchild %u\n", child);
}
// on uid events the uid is changing
if (proc_ev->what == PROC_EVENT_UID) {
if (pids[pid].user)
free(pids[pid].user);
pids[pid].user = 0;
pids[pid].uid = pid_get_uid(pid);
}
if (sandbox_closed)
exit(0);
}
}
return 0;
}
static void procevent_print_pids(void) {
// print files
int i;
for (i = 0; i < max_pids; i++) {
if (pids[i].level == 1)
pid_print_tree(i, 0, 1);
}
printf("\n");
}
void procevent(pid_t pid) {
// need to be root for this
if (getuid() != 0) {
fprintf(stderr, "Error: you need to be root to get process events\n");
exit(1);
}
// read and print sandboxed processes
pid_read(pid);
procevent_print_pids();
// monitor using netlink
int sock = procevent_netlink_setup();
if (sock < 0) {
fprintf(stderr, "Error: cannot open netlink socket\n");
exit(1);
}
procevent_monitor(sock, pid); // it will never return from here
assert(0);
close(sock); // quiet static analyzers
}

213
src/firemon/route.c Normal file
View file

@ -0,0 +1,213 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firemon.h"
#include <assert.h>
#include <arpa/inet.h>
#define MAXBUF 4096
typedef struct iflist_t {
struct iflist_t *next;
uint32_t ip;
} IfList;
static IfList *ifs = NULL;
static char last_start[MAXBUF + 1];
static IfList *list_find(uint32_t ip, uint32_t mask) {
IfList *ptr = ifs;
while (ptr) {
if ((ptr->ip & mask) == (ip & mask))
return ptr;
ptr = ptr->next;
}
return NULL;
}
static void extract_if(const char *fname) {
// clear interface list
while (ifs) {
IfList *tmp = ifs->next;
free(ifs);
ifs = tmp;
}
assert(ifs == NULL);
FILE *fp = fopen(fname, "r");
if (!fp)
return;
char buf[MAXBUF];
int state = 0; // 0 -wait for Local
//
while (fgets(buf, MAXBUF, fp)) {
// remove blanks, \n
char *ptr = buf;
while (*ptr == ' ' || *ptr == '\t')
ptr++;
char *start = ptr;
if (*start == '\0')
continue;
ptr = strchr(ptr, '\n');
if (ptr)
*ptr = '\0';
if (state == 0) {
if (strncmp(buf, "Local:", 6) == 0) {
state = 1;
continue;
}
}
else if (state == 1) {
// remove broadcast addresses
if (strstr(start,"BROADCAST"))
continue;
else if (*start == '+')
continue;
else if (*start == '|') {
memset(last_start, 0, MAXBUF + 1);
strncpy(last_start, start, MAXBUF);
continue;
}
else if (strstr(buf, "LOCAL")) {
// printf("%s %s\n", last_start, start);
unsigned mbits;
sscanf(start, "/%u", &mbits);
if (mbits != 32)
continue;
unsigned a, b, c, d;
if (sscanf(last_start, "|-- %u.%u.%u.%u", &a, &b, &c, &d) != 4 || a > 255 || b > 255 || c > 255 || d > 255)
continue;
IfList *newif = malloc(sizeof(IfList));
if (!newif)
errExit("malloc");
newif->ip = a * 0x1000000 + b * 0x10000 + c * 0x100 + d;
newif->next = ifs;
ifs = newif;
}
}
}
fclose(fp);
}
static void print_route(const char *fname) {
FILE *fp = fopen(fname, "r");
if (!fp)
return;
printf(" Route table:\n");
char buf[MAXBUF];
while (fgets(buf, MAXBUF, fp)) {
// remove blanks, \n
char *ptr = buf;
while (*ptr == ' ' || *ptr == '\t')
ptr++;
char *start = ptr;
if (*start == '\0')
continue;
ptr = strchr(ptr, '\n');
if (ptr)
*ptr = '\0';
// remove table header
//Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
if (strncmp(start, "Iface", 5) == 0)
continue;
// extract data
char ifname[64];
char destination[64];
char gateway[64];
char flags[64];
char refcnt[64];
char use[64];
char metric[64];
char mask[64];
int rv = sscanf(start, "%s %s %s %s %s %s %s %s\n", ifname, destination, gateway, flags, refcnt, use, metric, mask);
if (rv != 8)
continue;
// destination ip
uint32_t destip;
sscanf(destination, "%x", &destip);
destip = ntohl(destip);
uint32_t destmask;
sscanf(mask, "%x", &destmask);
destmask = ntohl(destmask);
uint32_t gw;
sscanf(gateway, "%x", &gw);
gw = ntohl(gw);
// printf("#%s# #%s# #%s# #%s# #%s# #%s# #%s# #%s#\n", ifname, destination, gateway, flags, refcnt, use, metric, mask);
if (gw != 0)
printf(" %u.%u.%u.%u/%u via %u.%u.%u.%u, dev %s, metric %s\n",
PRINT_IP(destip), mask2bits(destmask),
PRINT_IP(gw),
ifname,
metric);
else { // this is an interface
IfList *ifentry = list_find(destip, destmask);
if (ifentry) {
printf(" %u.%u.%u.%u/%u, dev %s, scope link src %d.%d.%d.%d\n",
PRINT_IP(destip), mask2bits(destmask),
ifname,
PRINT_IP(ifentry->ip));
}
}
}
fclose(fp);
}
void route(pid_t pid) {
if (getuid() == 0)
firemon_drop_privs();
pid_read(pid);
// print processes
int i;
for (i = 0; i < max_pids; i++) {
if (pids[i].level == 1) {
pid_print_list(i, 0);
int child = find_child(i);
if (child != -1) {
char *fname;
if (asprintf(&fname, "/proc/%d/net/fib_trie", child) == -1)
errExit("asprintf");
extract_if(fname);
free(fname);
if (asprintf(&fname, "/proc/%d/net/route", child) == -1)
errExit("asprintf");
print_route(fname);
free(fname);
printf("\n");
}
}
}
}

69
src/firemon/seccomp.c Normal file
View file

@ -0,0 +1,69 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firemon.h"
#define MAXBUF 4098
static void print_seccomp(int pid) {
char *file;
if (asprintf(&file, "/proc/%d/status", pid) == -1) {
errExit("asprintf");
exit(1);
}
FILE *fp = fopen(file, "r");
if (!fp) {
printf(" Error: cannot open %s\n", file);
free(file);
return;
}
char buf[MAXBUF];
while (fgets(buf, MAXBUF, fp)) {
if (strncmp(buf, "Seccomp:", 8) == 0) {
printf(" %s", buf);
fflush(0);
fclose(fp);
free(file);
return;
}
}
fclose(fp);
free(file);
}
void seccomp(pid_t pid) {
if (getuid() == 0)
firemon_drop_privs();
pid_read(pid); // include all processes
// print processes
int i;
for (i = 0; i < max_pids; i++) {
if (pids[i].level == 1) {
pid_print_list(i, 0);
int child = find_child(i);
if (child != -1)
print_seccomp(child);
}
}
printf("\n");
}

297
src/firemon/top.c Normal file
View file

@ -0,0 +1,297 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firemon.h"
#include <termios.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
static unsigned pgs_rss = 0;
static unsigned pgs_shared = 0;
static unsigned clocktick = 0;
static unsigned long long sysuptime = 0;
static char *get_header(void) {
char *rv;
if (asprintf(&rv, "%-5.5s %-9.9s %-8.8s %-8.8s %-5.5s %-4.4s %-9.9s %s",
"PID", "User", "RES(KiB)", "SHR(KiB)", "CPU%", "Prcs", "Uptime", "Command") == -1)
errExit("asprintf");
return rv;
}
// recursivity!!!
static char *print_top(unsigned index, unsigned parent, unsigned *utime, unsigned *stime, unsigned itv, float *cpu, int *cnt) {
char *rv = NULL;
char procdir[20];
snprintf(procdir, 20, "/proc/%u", index);
struct stat s;
if (stat(procdir, &s) == -1)
return NULL;
if (pids[index].level == 1) {
pgs_rss = 0;
pgs_shared = 0;
*utime = 0;
*stime = 0;
*cnt = 0;
}
(*cnt)++;
pid_getmem(index, &pgs_rss, &pgs_shared);
unsigned utmp;
unsigned stmp;
pid_get_cpu_time(index, &utmp, &stmp);
*utime += utmp;
*stime += stmp;
int i;
for (i = index + 1; i < max_pids; i++) {
if (pids[i].parent == index)
print_top(i, index, utime, stime, itv, cpu, cnt);
}
if (pids[index].level == 1) {
// pid
char pidstr[10];
snprintf(pidstr, 10, "%u", index);
// command
char *cmd = pid_proc_cmdline(index);
char *ptrcmd;
if (cmd == NULL) {
if (pids[index].zombie)
ptrcmd = "(zombie)";
else
ptrcmd = "";
}
else
ptrcmd = cmd;
// user
char *user = pid_get_user_name(pids[index].uid);
char *ptruser;
if (user)
ptruser = user;
else
ptruser = "";
// memory
int pgsz = getpagesize();
char rss[10];
snprintf(rss, 10, "%u", pgs_rss * pgsz / 1024);
char shared[10];
snprintf(shared, 10, "%u", pgs_shared * pgsz / 1024);
// uptime
unsigned long long uptime = pid_get_start_time(index);
if (clocktick == 0)
clocktick = sysconf(_SC_CLK_TCK);
uptime /= clocktick;
uptime = sysuptime - uptime;
unsigned sec = uptime % 60;
uptime -= sec;
uptime /= 60;
unsigned min = uptime % 60;
uptime -= min;
uptime /= 60;
unsigned hour = uptime;
char uptime_str[50];
snprintf(uptime_str, 50, "%02u:%02u:%02u", hour, min, sec);
// cpu
itv *= clocktick;
float ud = (float) (*utime - pids[index].utime) / itv * 100;
float sd = (float) (*stime - pids[index].stime) / itv * 100;
float cd = ud + sd;
*cpu = cd;
char cpu_str[10];
snprintf(cpu_str, 10, "%2.1f", cd);
// process count
char prcs_str[10];
snprintf(prcs_str, 10, "%d", *cnt);
if (asprintf(&rv, "%-5.5s %-9.9s %-8.8s %-8.8s %-5.5s %-4.4s %-9.9s %s",
pidstr, ptruser, rss, shared, cpu_str, prcs_str, uptime_str, ptrcmd) == -1)
errExit("asprintf");
if (cmd)
free(cmd);
if (user)
free(user);
}
return rv;
}
typedef struct node_t {
struct node_t *next;
char *line;
float cpu;
} Node;
static Node *head = NULL;
static void head_clear(void) {
Node *ptr = head;
while (ptr) {
if (ptr->line)
free(ptr->line);
Node *next = ptr->next;
free(ptr);
ptr = next;
}
head = NULL;
}
static void head_add(float cpu, char *line) {
// allocate a new node structure
Node *node = malloc(sizeof(Node));
if (!node)
errExit("malloc");
node->line = line;
node->cpu = cpu;
node->next = NULL;
// insert in first list position
if (head == NULL || head->cpu < cpu) {
node->next = head;
head = node;
return;
}
// insert in the right place
Node *ptr = head;
while (1) {
// last position
Node *current = ptr->next;
if (current == NULL) {
ptr->next = node;
return;
}
// current position
if (current->cpu < cpu) {
ptr->next = node;
node->next = current;
return;
}
ptr = current;
}
}
void head_print(int col, int row) {
Node *ptr = head;
int current = 0;
while (ptr) {
if (current >= row)
break;
if (strlen(ptr->line) > col)
ptr->line[col] = '\0';
if (ptr->next == NULL || current == (row - 1)) {
printf("%s", ptr->line);
fflush(0);
}
else
printf("%s\n", ptr->line);
ptr = ptr->next;
current++;
}
}
void top(void) {
if (getuid() == 0)
firemon_drop_privs();
while (1) {
// clear linked list
head_clear();
// set pid table
int i;
int itv = 5; // 5 second interval
pid_read(0);
// start cpu measurements
unsigned utime = 0;
unsigned stime = 0;
for (i = 0; i < max_pids; i++) {
if (pids[i].level == 1)
pid_store_cpu(i, 0, &utime, &stime);
}
// wait 5 seconds
firemon_sleep(itv);
// grab screen size
struct winsize sz;
int row = 24;
int col = 80;
if (!ioctl(0, TIOCGWINSZ, &sz)) {
col = sz.ws_col;
row = sz.ws_row;
}
// start printing
firemon_clrscr();
char *header = get_header();
if (strlen(header) > col)
header[col] = '\0';
printf("%s\n", header);
if (row > 0)
row--;
free(header);
// find system uptime
FILE *fp = fopen("/proc/uptime", "r");
if (fp) {
float f;
int rv = fscanf(fp, "%f", &f);
(void) rv;
sysuptime = (unsigned long long) f;
fclose(fp);
}
// print processes
for (i = 0; i < max_pids; i++) {
if (pids[i].level == 1) {
float cpu = 0;
int cnt = 0; // process count
char *line = print_top(i, 0, &utime, &stime, itv, &cpu, &cnt);
if (line)
head_add(cpu, line);
}
}
head_print(col, row);
}
}

36
src/firemon/tree.c Normal file
View file

@ -0,0 +1,36 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firemon.h"
void tree(pid_t pid) {
if (getuid() == 0)
firemon_drop_privs();
pid_read(pid); // include all processes
// print processes
int i;
for (i = 0; i < max_pids; i++) {
if (pids[i].level == 1)
pid_print_tree(i, 0, arg_nowrap);
}
printf("\n");
}

77
src/firemon/usage.c Normal file
View file

@ -0,0 +1,77 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firemon.h"
void usage(void) {
printf("firemon - version %s\n", VERSION);
printf("Usage: firemon [OPTIONS] [PID]\n\n");
printf("Monitor processes started in a Firejail sandbox. Without any PID specified,\n");
printf("all processes started by Firejail are monitored. Descendants of these processes\n");
printf("are also being monitored.\n\n");
printf("Options:\n");
printf("\t--arp - print ARP table for each sandbox.\n\n");
printf("\t--caps - print capabilities configuration for each sandbox.\n\n");
printf("\t--cgroup - print control group information for each sandbox.\n\n");
printf("\t--cpu - print CPU affinity for each sandbox.\n\n");
printf("\t--help, -? - this help screen.\n\n");
printf("\t--interface - print network interface information for each sandbox.\n\n");
printf("\t--list - list all sandboxes.\n\n");
printf("\t--name=name - print information only about named sandbox.\n\n");
printf("\t--netstats - monitor network statistics for sandboxes creating a new\n");
printf("\t\tnetwork namespace.\n\n");
printf("\t--route - print route table for each sandbox.\n\n");
printf("\t--seccomp - print seccomp configuration for each sandbox.\n\n");
printf("\t--tree - print a tree of all sandboxed processes.\n\n");
printf("\t--top - monitor the most CPU-intensive sandboxes.\n\n");
printf("\t--version - print program version and exit.\n\n");
printf("Without any options, firemon monitors all fork, exec, id change, and exit events\n");
printf("in the sandbox. Monitoring a specific PID is also supported.\n\n");
printf("Option --list prints a list of all sandboxes. The format for each entry is as\n");
printf("follows:\n\n");
printf("\tPID:USER:Command\n\n");
printf("Option --tree prints the tree of processes running in the sandbox. The format\n");
printf("for each process entry is as follows:\n\n");
printf("\tPID:USER:Command\n\n");
printf("Option --top is similar to the UNIX top command, however it applies only to\n");
printf("sandboxes. Listed below are the available fields (columns) in alphabetical\n");
printf("order:\n\n");
printf("\tCommand - command used to start the sandbox.\n");
printf("\tCPU%% - CPU usage, the sandbox share of the elapsed CPU time since the\n");
printf("\t last screen update\n");
printf("\tPID - Unique process ID for the task controlling the sandbox.\n");
printf("\tPrcs - number of processes running in sandbox, including the controlling\n");
printf("\t process.\n");
printf("\tRES - Resident Memory Size (KiB), sandbox non-swapped physical memory.\n");
printf("\t It is a sum of the RES values for all processes running in the\n");
printf("\t sandbox.\n");
printf("\tSHR - Shared Memory Size (KiB), it reflects memory shared with other\n");
printf("\t processes. It is a sum of the SHR values for all processes running\n");
printf("\t in the sandbox, including the controlling process.\n");
printf("\tUptime - sandbox running time in hours:minutes:seconds format.\n");
printf("\tUser - The owner of the sandbox.\n");
printf("\n");
printf("License GPL version 2 or later\n");
printf("Homepage: http://firejail.sourceforge.net\n");
printf("\n");
}

69
src/fshaper/fshaper.sh Executable file
View file

@ -0,0 +1,69 @@
#!/bin/bash
usage() {
echo "Usage:"
echo " fshaper.sh --status"
echo " fshaper.sh --clear device"
echo " fshaper.sh --set device download-speed upload-speed"
}
if [ "$1" = "--status" ]; then
/sbin/tc -s qdisc ls
/sbin/tc -s class ls
exit
fi
if [ "$1" = "--clear" ]; then
if [ $# -ne 2 ]; then
echo "Error: invalid command"
usage
exit
fi
DEV=$2
echo "Removing bandwith limits"
/sbin/tc qdisc del dev $DEV root 2> /dev/null > /dev/null
/sbin/tc qdisc del dev $DEV ingress 2> /dev/null > /dev/null
exit
fi
if [ "$1" = "--set" ]; then
DEV=$2
echo "Removing bandwith limit"
/sbin/tc qdisc del dev $DEV ingress #2> /dev/null > /dev/null
if [ $# -ne 4 ]; then
echo "Error: missing parameters"
usage
exit
fi
DEV=$2
echo "Configuring interface $DEV "
IN=$3
IN=$((${IN} * 8))
echo "Download speed ${IN}kbps"
OUT=$4
OUT=$((${OUT} * 8))
echo "Upload speed ${OUT}kbps"
echo "cleaning limits"
/sbin/tc qdisc del dev $DEV root 2> /dev/null > /dev/null
/sbin/tc qdisc del dev $DEV ingress 2> /dev/null > /dev/null
echo "configuring tc ingress"
/sbin/tc qdisc add dev $DEV handle ffff: ingress #2> /dev/null > /dev/null
/sbin/tc filter add dev $DEV parent ffff: protocol ip prio 50 u32 match ip src \
0.0.0.0/0 police rate ${IN}kbit burst 10k drop flowid :1 #2> /dev/null > /dev/null
echo "configuring tc egress"
/sbin/tc qdisc add dev $DEV root tbf rate ${OUT}kbit latency 25ms burst 10k #2> /dev/null > /dev/null
exit
fi
echo "Error: missing parameters"
usage
exit 1

24
src/ftee/Makefile.in Normal file
View file

@ -0,0 +1,24 @@
all: ftee
PREFIX=@prefix@
VERSION=@PACKAGE_VERSION@
NAME=@PACKAGE_NAME@
H_FILE_LIST = $(wildcard *.[h])
C_FILE_LIST = $(wildcard *.c)
OBJS = $(C_FILE_LIST:.c=.o)
BINOBJS = $(foreach file, $(OBJS), $file)
CFLAGS += -ggdb -O2 -DVERSION='"$(VERSION)"' -DPREFIX='"$(PREFIX)"' -fstack-protector-all -D_FORTIFY_SOURCE=2 -fPIE -pie -Wformat -Wformat-security
LDFLAGS += -pie -Wl,-z,relro -Wl,-z,now -lpthread
%.o : %.c $(H_FILE_LIST)
$(CC) $(CFLAGS) $(INCLUDE) -c $< -o $@
ftee: $(OBJS)
$(CC) $(LDFLAGS) -o $@ $(OBJS)
clean:; rm -f *.o ftee
distclean: clean
rm -fr Makefile

24
src/ftee/ftee.h Normal file
View file

@ -0,0 +1,24 @@
/*
* Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com)
*
* This file is part of firejail project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef FTEE_H
#define FTEE_H
#include "../include/common.h"
#endif

228
src/ftee/main.c Normal file
View file

@ -0,0 +1,228 @@
#include "ftee.h"
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#define MAXBUF 512
static unsigned char buf[MAXBUF];
static FILE *out_fp = NULL;
static int out_cnt = 0;
static int out_max = 500 * 1024;
static void log_close(void) {
if (out_fp) {
fclose(out_fp);
out_fp = NULL;
}
}
static void log_rotate(const char *fname) {
struct stat s;
int index = strlen(fname);
char *name1 = malloc(index + 2 + 1);
char *name2 = malloc(index + 2 + 1);
if (!name1 || !name2)
errExit("malloc");
strcpy(name1, fname);
strcpy(name2, fname);
fflush(0);
// delete filename.5
sprintf(name1 + index, ".5");
if (stat(name1, &s) == 0) {
int rv = unlink(name1);
if (rv == -1)
perror("unlink");
}
// move files 1 to 4 down one position
sprintf(name2 + index, ".4");
if (stat(name2, &s) == 0) {
int rv = rename(name2, name1);
if (rv == -1)
perror("rename");
}
sprintf(name1 + index, ".3");
if (stat(name1, &s) == 0) {
int rv = rename(name1, name2);
if (rv == -1)
perror("rename");
}
sprintf(name2 + index, ".2");
if (stat(name2, &s) == 0) {
/* coverity[toctou] */
int rv = rename(name2, name1);
if (rv == -1)
perror("rename");
}
sprintf(name1 + index, ".1");
if (stat(name1, &s) == 0) {
int rv = rename(name1, name2);
if (rv == -1)
perror("rename");
}
// move the first file
if (out_fp)
fclose(out_fp);
out_fp = NULL;
if (stat(fname, &s) == 0) {
int rv = rename(fname, name1);
if (rv == -1)
perror("rename");
}
free(name1);
free(name2);
}
static void log_write(const unsigned char *str, int len, const char *fname) {
assert(fname);
if (out_fp == NULL) {
out_fp = fopen(fname, "w");
if (!out_fp) {
fprintf(stderr, "Error: cannot open log file %s\n", fname);
exit(1);
}
out_cnt = 0;
}
// rotate files
out_cnt += len;
if (out_cnt >= out_max) {
log_rotate(fname);
// reopen the first file
if (out_fp)
fclose(out_fp);
out_fp = fopen(fname, "w");
if (!out_fp) {
fprintf(stderr, "Error: cannot open log file %s\n", fname);
exit(1);
}
out_cnt = len;
}
fwrite(str, len, 1, out_fp);
fflush(0);
}
// return 1 if the file is a directory
static int is_dir(const char *fname) {
assert(fname);
if (*fname == '\0')
return 0;
// if fname doesn't end in '/', add one
int rv;
struct stat s;
if (fname[strlen(fname) - 1] == '/')
rv = stat(fname, &s);
else {
char *tmp;
if (asprintf(&tmp, "%s/", fname) == -1) {
fprintf(stderr, "Error: cannot allocate memory, %s:%d\n", __FILE__, __LINE__);
exit(1);
}
rv = stat(tmp, &s);
free(tmp);
}
if (rv == -1)
return 0;
if (S_ISDIR(s.st_mode))
return 1;
return 0;
}
// return 1 if the file is a link
static int is_link(const char *fname) {
assert(fname);
if (*fname == '\0')
return 0;
struct stat s;
if (lstat(fname, &s) == 0) {
if (S_ISLNK(s.st_mode))
return 1;
}
return 0;
}
static void usage(void) {
printf("Usage: ftee filename\n");
}
int main(int argc, char **argv) {
if (argc < 2) {
fprintf(stderr, "Error: please provide a filename to store the program output\n");
usage();
exit(1);
}
char *fname = argv[1];
// do not accept directories, links, and files with ".."
if (strstr(fname, "..") || is_link(fname) || is_dir(fname)) {
fprintf(stderr, "Error: invalid output file. Links, directories and files with \"..\" are not allowed.\n");
exit(1);
}
struct stat s;
if (stat(fname, &s) == 0) {
// check permissions
if (s.st_uid != getuid() || s.st_gid != getgid()) {
fprintf(stderr, "Error: the output file needs to be owned by the current user.\n");
exit(1);
}
// check hard links
if (s.st_nlink != 1) {
fprintf(stderr, "Error: no hard links allowed.\n");
exit(1);
}
}
// check if we can append to this file
/* coverity[toctou] */
FILE *fp = fopen(fname, "a");
if (!fp) {
fprintf(stderr, "Error: cannot open output file %s\n", fname);
exit(1);
}
fclose(fp);
// preserve the last log file
log_rotate(fname);
setvbuf (stdout, NULL, _IONBF, 0);
while(1) {
int n = read(0, buf, sizeof(buf));
if (n < 0 && errno == EINTR)
continue;
if (n <= 0)
break;
fwrite(buf, n, 1, stdout);
log_write(buf, n, fname);
}
log_close();
return 0;
}

Some files were not shown because too many files have changed in this diff Show more