[GH-ISSUE #3598] Adding binaries outside standard "bin dirs" ala private-bin? #2255

Closed
opened 2026-05-05 08:56:51 -06:00 by gitea-mirror · 2 comments
Owner

Originally created by @curiositycasualty on GitHub (Aug 20, 2020).
Original GitHub issue: https://github.com/netblue30/firejail/issues/3598

I've been tasked with sandboxing R (on Ubuntu 20.04). I've already got a trustworthy AppArmor profile that seems to fit over-top of firejail correctly; except AppArmor doesn't offer much in the way of network-sandboxing and my sandboxed R ultimately needs to be able to talk to a webserver on localhost over ports 80 and 443. So firejail's networking capability and /etc/firejail/webserver.net piqued my interest. The webserver's R integration is either through an R package/client (needing to talk to localhost on ports 80 and 443), And/or writing an R script and input files to disk, executing the R script, and then reading the output file generated by executing the R script. The firejail sandboxing needs to support both methods.

However, I think I'm running into a devilishly devised snare caused by a combination of R's overlapping binary names and firejail's generous private-* search behaviour.

R essentially has a single, binary interpreter: /usr/lib/R/bin/exec/R but wraps it in a handful of shell scripts, some of which are also named R (like: /usr/bin/R and /usr/lib/R/bin/R):

$ for path in /usr/{bin/R,lib/R/bin/{R,exec/R}}; do md5sum $path; file $path; done
85dbed86ecb6e7eb4d1e6c723791a8c8  /usr/bin/R
/usr/bin/R: Bourne-Again shell script, ASCII text executable
b04165152acad17d8cbbee9a42d5247b  /usr/lib/R/bin/R
/usr/lib/R/bin/R: Bourne-Again shell script, ASCII text executable
2fadb0933787a5c06ea6d9bbeb4dd837  /usr/lib/R/bin/exec/R
/usr/lib/R/bin/exec/R: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=4b219bb915d9f3306be0b183bf78e52c3b50a5d8, for GNU/Linux 3.2.0, stripped

For the moment, I'm willfully ignoring the libraries potentially compiled/installed by installed R packages, which generally wind up in a lib dir under the package's own dir within /usr/local/lib/R/site-library:

$ sudo find /usr/local/lib/R/site-library/ -name '*.so*'
/usr/local/lib/R/site-library/digest/libs/digest.so
/usr/local/lib/R/site-library/htmltools/libs/htmltools.so
/usr/local/lib/R/site-library/markdown/libs/markdown.so
/usr/local/lib/R/site-library/jsonlite/libs/jsonlite.so
/usr/local/lib/R/site-library/rlang/libs/rlang.so
/usr/local/lib/R/site-library/base64enc/libs/base64enc.so
/usr/local/lib/R/site-library/yaml/libs/yaml.so
/usr/local/lib/R/site-library/xfun/libs/xfun.so
/usr/local/lib/R/site-library/glue/libs/glue.so
/usr/local/lib/R/site-library/mime/libs/mime.so
/usr/local/lib/R/site-library/stringi/libs/stringi.so

Where I'm stuck is trying to construct a profile with a private-bin and a private-lib that accommodates /usr/lib/R/bin/exec/R's runtime libraries:

$ R_HOME=/usr/lib/R ldd /usr/lib/R/bin/exec/R
	linux-vdso.so.1 (0x00007fff11b59000)
	libR.so => /lib/libR.so (0x00007fc793f9f000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc793dad000)
	libblas.so.3 => /lib/x86_64-linux-gnu/libblas.so.3 (0x00007fc7923ae000)
	libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fc79225f000)
	libreadline.so.8 => /lib/x86_64-linux-gnu/libreadline.so.8 (0x00007fc79220f000)
	libpcre.so.3 => /lib/x86_64-linux-gnu/libpcre.so.3 (0x00007fc79219c000)
	liblzma.so.5 => /lib/x86_64-linux-gnu/liblzma.so.5 (0x00007fc792173000)
	libbz2.so.1.0 => /lib/x86_64-linux-gnu/libbz2.so.1.0 (0x00007fc792160000)
	libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007fc792144000)
	libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fc79213e000)
	libicuuc.so.66 => /lib/x86_64-linux-gnu/libicuuc.so.66 (0x00007fc791f58000)
	libicui18n.so.66 => /lib/x86_64-linux-gnu/libicui18n.so.66 (0x00007fc791c59000)
	libgomp.so.1 => /lib/x86_64-linux-gnu/libgomp.so.1 (0x00007fc791c15000)
	libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fc791bf2000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fc79444d000)
	libtinfo.so.6 => /lib/x86_64-linux-gnu/libtinfo.so.6 (0x00007fc791bc2000)
	libicudata.so.66 => /lib/x86_64-linux-gnu/libicudata.so.66 (0x00007fc790101000)
	libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fc78ff20000)
	libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fc78ff03000)

It seems to me that binaries along private-bin and libs along private-lib paths are scanned with ldd to collect runtime library requirements, but since the R interpreter sits under /usr/lib/..., I'm unable to add it to a private-bin line in my profile.

I'm also concerned that upon finding multiple "R"s, firejail is overwriting them when placing them into the jail-- as there seems to be an implicit assumption that there will only ever be 1 binary of a given name (or there only the first binary of a given name will be added to the jail) found along PATH.

I'm in a special kind of hell. Please help me.

Originally created by @curiositycasualty on GitHub (Aug 20, 2020). Original GitHub issue: https://github.com/netblue30/firejail/issues/3598 I've been tasked with sandboxing R (on Ubuntu 20.04). I've already got a trustworthy AppArmor profile that seems to fit over-top of firejail correctly; except AppArmor doesn't offer much in the way of network-sandboxing and my sandboxed R ultimately needs to be able to talk to a webserver on localhost over ports `80` and `443`. So firejail's networking capability and `/etc/firejail/webserver.net` piqued my interest. The webserver's R integration is either through an R package/client (needing to talk to localhost on ports `80` and `443`), And/or writing an R script and input files to disk, executing the R script, and then reading the output file generated by executing the R script. The firejail sandboxing needs to support both methods. However, I think I'm running into a devilishly devised snare caused by a combination of R's overlapping binary names and firejail's generous `private-*` search behaviour. R essentially has a single, binary interpreter: `/usr/lib/R/bin/exec/R` but wraps it in a handful of shell scripts, some of which are also named `R` (like: `/usr/bin/R` and `/usr/lib/R/bin/R`): ``` $ for path in /usr/{bin/R,lib/R/bin/{R,exec/R}}; do md5sum $path; file $path; done 85dbed86ecb6e7eb4d1e6c723791a8c8 /usr/bin/R /usr/bin/R: Bourne-Again shell script, ASCII text executable b04165152acad17d8cbbee9a42d5247b /usr/lib/R/bin/R /usr/lib/R/bin/R: Bourne-Again shell script, ASCII text executable 2fadb0933787a5c06ea6d9bbeb4dd837 /usr/lib/R/bin/exec/R /usr/lib/R/bin/exec/R: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=4b219bb915d9f3306be0b183bf78e52c3b50a5d8, for GNU/Linux 3.2.0, stripped ``` For the moment, I'm willfully ignoring the libraries potentially compiled/installed by installed R packages, which generally wind up in a `lib` dir under the package's own dir within `/usr/local/lib/R/site-library`: ``` $ sudo find /usr/local/lib/R/site-library/ -name '*.so*' /usr/local/lib/R/site-library/digest/libs/digest.so /usr/local/lib/R/site-library/htmltools/libs/htmltools.so /usr/local/lib/R/site-library/markdown/libs/markdown.so /usr/local/lib/R/site-library/jsonlite/libs/jsonlite.so /usr/local/lib/R/site-library/rlang/libs/rlang.so /usr/local/lib/R/site-library/base64enc/libs/base64enc.so /usr/local/lib/R/site-library/yaml/libs/yaml.so /usr/local/lib/R/site-library/xfun/libs/xfun.so /usr/local/lib/R/site-library/glue/libs/glue.so /usr/local/lib/R/site-library/mime/libs/mime.so /usr/local/lib/R/site-library/stringi/libs/stringi.so ``` Where I'm stuck is trying to construct a profile with a `private-bin` and a `private-lib` that accommodates `/usr/lib/R/bin/exec/R`'s runtime libraries: ``` $ R_HOME=/usr/lib/R ldd /usr/lib/R/bin/exec/R linux-vdso.so.1 (0x00007fff11b59000) libR.so => /lib/libR.so (0x00007fc793f9f000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc793dad000) libblas.so.3 => /lib/x86_64-linux-gnu/libblas.so.3 (0x00007fc7923ae000) libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fc79225f000) libreadline.so.8 => /lib/x86_64-linux-gnu/libreadline.so.8 (0x00007fc79220f000) libpcre.so.3 => /lib/x86_64-linux-gnu/libpcre.so.3 (0x00007fc79219c000) liblzma.so.5 => /lib/x86_64-linux-gnu/liblzma.so.5 (0x00007fc792173000) libbz2.so.1.0 => /lib/x86_64-linux-gnu/libbz2.so.1.0 (0x00007fc792160000) libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007fc792144000) libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fc79213e000) libicuuc.so.66 => /lib/x86_64-linux-gnu/libicuuc.so.66 (0x00007fc791f58000) libicui18n.so.66 => /lib/x86_64-linux-gnu/libicui18n.so.66 (0x00007fc791c59000) libgomp.so.1 => /lib/x86_64-linux-gnu/libgomp.so.1 (0x00007fc791c15000) libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fc791bf2000) /lib64/ld-linux-x86-64.so.2 (0x00007fc79444d000) libtinfo.so.6 => /lib/x86_64-linux-gnu/libtinfo.so.6 (0x00007fc791bc2000) libicudata.so.66 => /lib/x86_64-linux-gnu/libicudata.so.66 (0x00007fc790101000) libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fc78ff20000) libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fc78ff03000) ``` It seems to me that binaries along `private-bin` and libs along `private-lib` paths are scanned with `ldd` to collect runtime library requirements, but since the R interpreter sits under `/usr/lib/...`, I'm unable to add it to a `private-bin` line in my profile. I'm also concerned that upon finding multiple "`R`"s, firejail is overwriting them when placing them into the jail-- as there seems to be an implicit assumption that there will only ever be 1 binary of a given name (or there only the first binary of a given name will be added to the jail) found along PATH. I'm in a special kind of hell. Please help me.
Author
Owner

@bbhtt commented on GitHub (Aug 21, 2020):

For the moment, I'm willfully ignoring the libraries potentially compiled/installed by installed R packages

You can choose that location, for example when I was making a profile, R choose the my shell dir working directory, private-lib is unwritable so it installed packages to ~/R/x86_64-pc-linux-gnu-library/4.0/. This will need to set noexec home and tmp (the packages are downloaded to /tmp/RTmpXXXX).

Where I'm stuck is trying to construct a profile with a private-bin and a private-lib that accommodates /usr/lib/R/bin/exec/R's runtime libraries...

You can express directories relative to /lib to private-lib: Here's mine that I used to download: install.packages("Rcmdr", dependencies=TRUE)

private-lib R,libreadline.so.8,libblas.so.3,libpcre2-8.so.0,libpcre2-8.so.0,liblzma.so.5,libbz2.so.1.0,libz.so.1,libtirpc.so.3,libtirpc.so.3,libicuuc.so.67,libicui18n.so.67,libgomp.so.1,liblapack.so.3,libcurl.so.4,libtiff.so.5

and my:

private-bin R,bash,less,lessecho,sh,uname,which,rm,tar,sed,mv
private-etc alternatives,ca-certificates,crypto-policies,host.conf,hostname,hosts,inputrc,ld.so.cache,ld.so.conf,ld.so.conf.d,ld.so.preload,locale,locale.alias,locale.conf,os-release,mime.types,nsswitch.conf,pki,resolv.conf,R,Renviron.site,Rprofile.site,selinux,ssl,sysless

The three above probably need some refining...

I think, making a profile for it doesn't seem practical,since it acts as a package manager too, it'll need exeutables/libs to be listed depending on the package you install and this profile won't be portable to another system/distro. And private-lib only works on amd64 I think.

<!-- gh-comment-id:678040144 --> @bbhtt commented on GitHub (Aug 21, 2020): > For the moment, I'm willfully ignoring the libraries potentially compiled/installed by installed R packages You can choose that location, for example when I was making a profile, R choose the my shell dir working directory, `private-lib` is unwritable so it installed packages to `~/R/x86_64-pc-linux-gnu-library/4.0/`. This will need to set `noexec` home and tmp (the packages are downloaded to /tmp/RTmpXXXX). > Where I'm stuck is trying to construct a profile with a private-bin and a private-lib that accommodates /usr/lib/R/bin/exec/R's runtime libraries... You can express directories relative to `/lib` to `private-lib`: Here's mine that I used to download: `install.packages("Rcmdr", dependencies=TRUE)` ``` private-lib R,libreadline.so.8,libblas.so.3,libpcre2-8.so.0,libpcre2-8.so.0,liblzma.so.5,libbz2.so.1.0,libz.so.1,libtirpc.so.3,libtirpc.so.3,libicuuc.so.67,libicui18n.so.67,libgomp.so.1,liblapack.so.3,libcurl.so.4,libtiff.so.5 ``` and my: ``` private-bin R,bash,less,lessecho,sh,uname,which,rm,tar,sed,mv ``` ``` private-etc alternatives,ca-certificates,crypto-policies,host.conf,hostname,hosts,inputrc,ld.so.cache,ld.so.conf,ld.so.conf.d,ld.so.preload,locale,locale.alias,locale.conf,os-release,mime.types,nsswitch.conf,pki,resolv.conf,R,Renviron.site,Rprofile.site,selinux,ssl,sysless ``` The three above probably need some refining... I think, making a profile for it doesn't seem practical,since it acts as a package manager too, it'll need exeutables/libs to be listed depending on the package you install and this profile won't be portable to another system/distro. And `private-lib` only works on amd64 I think.
Author
Owner

@curiositycasualty commented on GitHub (Sep 28, 2020):

I guess I should close the loop. Thank you @kortewegdevries for your suggestions.

What I ended up doing was using ldd on /usr/lib/R/bin/exec/R to generate a list of libs for a firejail profile. In my case:

exec_R_libs: %x(ldd /usr/lib/R/bin/exec/R).lines.map do |line|
  line.match(/\/([^ \/]+)(?: \(0x)/)
end.reject(&:nil?).map{|match| match[1]}.join(',')

This is for my very narrow use-case of an <app> that is a tomcat application w/ R script and execution integration.
Not pictured here:

  • creation of a br0 interface
  • a dummy /usr/local/bin/shims/uname replacement (because R can be dumb)
  • restricted-network no in /etc/firejail/firejail.config to allow for net br0
  • echo 0 > /proc/sys/net/ipv4/ip_forward so that localhost is the only reachable thing via br0
  • an /etc/firejail/R.hosts file so that the sandbox can do some DNS resolution "transparently"
  • a custom /etc/apparmor.d/use.bin.R apparmor profile covering R and Rscript
  • and probably half-dozen other little details I've happily forgotten since working on this

In so far as I can tell, it supports xvfb use and pandoc's pdf generation.

# Firejail profile for R
# Description: general markup converter

name r-sandbox
join-or-start r-sandbox

include disable-common.inc
include disable-devel.inc
include disable-exec.inc
include disable-interpreters.inc
include disable-passwdmgr.inc
include disable-programs.inc
include disable-xdg.inc

# breaks pdf output
# include whitelist-usr-share-common.inc

# although its tempting to use pre-baked profiles, many include "net none"
# which overlaps with our R profile
# include pandoc.profile

# needed since tomcat user shell is /bin/false
shell none

env R_HOME=/usr/lib/R
env R_HOME_DIR=/usr/lib/R

env CURLOPT_TIMEOUT=3
env CURLOPT_VERBOSE=1

nodvd
nosound
noautopulse
notv
nou2f
no3d

noroot

quiet

machine-id

x11 xvfb

hostname sandbox

hosts-file /etc/firejail/R.hosts
net br0

private-bin R,Rscript,basename,bash,bzip2,cat,curl,dash,mktexfmt,pandoc,pdflatex,pdftex,rm,sed,sh,tar,unzip,wget,which,zip,shims/uname

private-etc R,alternatives,ca-certificates,host.conf,hostname,hosts,inputrc,ld.so.cache,ld.so.conf,ld.so.conf.d,ld.so.preload,locale.alias,mime.types,nsswitch.conf,os-release,pki,resolv.conf,selinux,ssl,localtime,fonts,services,pandoc,texmf,texlive

private-lib R,R/modules/*.so,R/library/*/libs/*.so,R/site-library/*/libs/*.so,libR.so,libc.so.6,libblas.so.3,libm.so.6,libreadline.so.8,libpcre.so.3,liblzma.so.5,libbz2.so.1.0,libz.so.1,libdl.so.2,libicuuc.so.66,libicui18n.so.66,libgomp.so.1,libpthread.so.0,ld-linux-x86-64.so.2,libtinfo.so.6,libicudata.so.66,libstdc++.so.6,libgcc_s.so.1,x86_64-linux-gnu/openblas-pthread/libblas.so.3,x86_64-linux-gnu/lapack/liblapack.so.3

private-tmp

private-dev

whitelist /usr/share/pandoc
whitelist /var/lib/pandoc
whitelist /usr/share/texmf
whitelist /var/lib/texmf
whitelist /usr/share/texlive
whitelist /var/lib/texlive

read-only /usr/local/share/R

read-write /<app>/tomcat-tmp/reports_temp
read-only /<app>/<app>/files

noexec /tmp
noexec ${HOME}

<!-- gh-comment-id:700335568 --> @curiositycasualty commented on GitHub (Sep 28, 2020): I guess I should close the loop. Thank you @kortewegdevries for your suggestions. What I ended up doing was using `ldd` on `/usr/lib/R/bin/exec/R` to generate a list of libs for a firejail profile. In my case: ```ruby exec_R_libs: %x(ldd /usr/lib/R/bin/exec/R).lines.map do |line| line.match(/\/([^ \/]+)(?: \(0x)/) end.reject(&:nil?).map{|match| match[1]}.join(',') ``` This is for my very narrow use-case of an `<app>` that is a tomcat application w/ R script and execution integration. Not pictured here: - creation of a `br0` interface - a dummy `/usr/local/bin/shims/uname` replacement (because R can be dumb) - `restricted-network no` in `/etc/firejail/firejail.config` to allow for `net br0` - `echo 0 > /proc/sys/net/ipv4/ip_forward` so that localhost is the only reachable thing via `br0` - an `/etc/firejail/R.hosts` file so that the sandbox can do some DNS resolution "transparently" - a custom `/etc/apparmor.d/use.bin.R` apparmor profile covering `R` and `Rscript` - and probably half-dozen other little details I've happily forgotten since working on this In so far as I can tell, it supports `xvfb` use and pandoc's `pdf` generation. ``` # Firejail profile for R # Description: general markup converter name r-sandbox join-or-start r-sandbox include disable-common.inc include disable-devel.inc include disable-exec.inc include disable-interpreters.inc include disable-passwdmgr.inc include disable-programs.inc include disable-xdg.inc # breaks pdf output # include whitelist-usr-share-common.inc # although its tempting to use pre-baked profiles, many include "net none" # which overlaps with our R profile # include pandoc.profile # needed since tomcat user shell is /bin/false shell none env R_HOME=/usr/lib/R env R_HOME_DIR=/usr/lib/R env CURLOPT_TIMEOUT=3 env CURLOPT_VERBOSE=1 nodvd nosound noautopulse notv nou2f no3d noroot quiet machine-id x11 xvfb hostname sandbox hosts-file /etc/firejail/R.hosts net br0 private-bin R,Rscript,basename,bash,bzip2,cat,curl,dash,mktexfmt,pandoc,pdflatex,pdftex,rm,sed,sh,tar,unzip,wget,which,zip,shims/uname private-etc R,alternatives,ca-certificates,host.conf,hostname,hosts,inputrc,ld.so.cache,ld.so.conf,ld.so.conf.d,ld.so.preload,locale.alias,mime.types,nsswitch.conf,os-release,pki,resolv.conf,selinux,ssl,localtime,fonts,services,pandoc,texmf,texlive private-lib R,R/modules/*.so,R/library/*/libs/*.so,R/site-library/*/libs/*.so,libR.so,libc.so.6,libblas.so.3,libm.so.6,libreadline.so.8,libpcre.so.3,liblzma.so.5,libbz2.so.1.0,libz.so.1,libdl.so.2,libicuuc.so.66,libicui18n.so.66,libgomp.so.1,libpthread.so.0,ld-linux-x86-64.so.2,libtinfo.so.6,libicudata.so.66,libstdc++.so.6,libgcc_s.so.1,x86_64-linux-gnu/openblas-pthread/libblas.so.3,x86_64-linux-gnu/lapack/liblapack.so.3 private-tmp private-dev whitelist /usr/share/pandoc whitelist /var/lib/pandoc whitelist /usr/share/texmf whitelist /var/lib/texmf whitelist /usr/share/texlive whitelist /var/lib/texlive read-only /usr/local/share/R read-write /<app>/tomcat-tmp/reports_temp read-only /<app>/<app>/files noexec /tmp noexec ${HOME} ```
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: github-starred/firejail#2255
No description provided.