Bug 4207

Summary: Switch to using busybox instead of tinylogin
Product: [Build System, Metadata & Runtime] OE-Core Reporter: Saul Wold <sgw>
Component: coreAssignee: Chen Qi <Qi.Chen>
Status: VERIFIED FIXED QA Contact:
Severity: enhancement    
Priority: Medium+ CC: meta.mr.watcher, meta.watcher, mihai.lindner, net147, Qi.Chen
Version: 1.5   
Target Milestone: 1.5 M2   
Hardware: x86   
OS: Multiple   
Whiteboard:
OS type for building Yocto: --- Type of Regression: ---
Verified: Documentation change: ---
Bug Depends on: 4570    
Bug Blocks: 4663    

Description Saul Wold 2013-04-05 17:46:32 UTC
tinylogin has been deprecated and the functionality ported into busybox, the size is smaller than tinybox. The issue to be concerned about is having busybox being setuid, a possibility is to use busybox as a separate binary.
Comment 1 Jonathan Liu 2013-05-20 04:16:08 UTC
This would probably solve some sulogin issues I am having with logging into systemd emergency mode:
# systemctl emergency

If the root password is blank:
-It still asks for root password and fails to login if you just press enter. As a result, it goes back to normal startup so you can never actually enter emergency mode with empty password.
-If you type anything and press enter, it crashes with a segmentation fault:
 sulogin[572]: segfault at 0 ip 41b53ce1 sp bfa3b38c error 4 in libc-2.17.so[41ad6000+171000]
Comment 2 Chen Qi 2013-05-23 08:57:38 UTC
In current project, the default file permission for busybox is 4755, as it should support commands such as 'ping' which require temporary user privilege elevation.

So the design seems easy.
1) change the default login manager from 'tinylogin' to 'busybox'.
2) change the defconfig file of busybox to include commands which were provided by tinylogin previously.

Following's a raw patch.

+++ b/meta/recipes-core/busybox/busybox-1.20.2/defconfig
@@ -454,34 +454,34 @@ CONFIG_INIT_TERMINAL_TYPE=""
 # CONFIG_USE_BB_SHADOW is not set
 CONFIG_USE_BB_CRYPT=y
 # CONFIG_USE_BB_CRYPT_SHA is not set
-# CONFIG_ADDUSER is not set
-# CONFIG_FEATURE_ADDUSER_LONG_OPTIONS is not set
+CONFIG_ADDUSER=y
+CONFIG_FEATURE_ADDUSER_LONG_OPTIONS=y
 # CONFIG_FEATURE_CHECK_NAMES is not set
 CONFIG_FIRST_SYSTEM_ID=0
 CONFIG_LAST_SYSTEM_ID=0
-# CONFIG_ADDGROUP is not set
-# CONFIG_FEATURE_ADDGROUP_LONG_OPTIONS is not set
+CONFIG_ADDGROUP=y
+CONFIG_FEATURE_ADDGROUP_LONG_OPTIONS=y
 # CONFIG_FEATURE_ADDUSER_TO_GROUP is not set
-# CONFIG_DELUSER is not set
-# CONFIG_DELGROUP is not set
+CONFIG_DELUSER=y
+CONFIG_DELGROUP=y
 # CONFIG_FEATURE_DEL_USER_FROM_GROUP is not set
-# CONFIG_GETTY is not set
-# CONFIG_LOGIN is not set
+CONFIG_GETTY=y
+CONFIG_LOGIN=y
 # CONFIG_LOGIN_SESSION_AS_CHILD is not set
 # CONFIG_PAM is not set
 # CONFIG_LOGIN_SCRIPTS is not set
 # CONFIG_FEATURE_NOLOGIN is not set
 # CONFIG_FEATURE_SECURETTY is not set
-# CONFIG_PASSWD is not set
+CONFIG_PASSWD=y
 # CONFIG_FEATURE_PASSWD_WEAK_CHECK is not set
 # CONFIG_CRYPTPW is not set
 # CONFIG_CHPASSWD is not set
 CONFIG_FEATURE_DEFAULT_PASSWD_ALGO=""
-# CONFIG_SU is not set
+CONFIG_SU=y
 # CONFIG_FEATURE_SU_SYSLOG is not set
 # CONFIG_FEATURE_SU_CHECKS_SHELLS is not set
-# CONFIG_SULOGIN is not set
-# CONFIG_VLOCK is not set
+CONFIG_SULOGIN=y
+CONFIG_VLOCK=y
 
 #
 # Linux Ext2 FS Progs
diff --git a/meta/recipes-core/packagegroups/packagegroup-core-boot.bb b/meta/recipes-core/packagegroups/packagegroup-core-boot.bb
index 9306a34..d69c41d 100644
--- a/meta/recipes-core/packagegroups/packagegroup-core-boot.bb
+++ b/meta/recipes-core/packagegroups/packagegroup-core-boot.bb
@@ -25,7 +25,7 @@ RCONFLICTS_${PN} = "task-core-boot"
 
 # Distro can override the following VIRTUAL-RUNTIME providers:
 VIRTUAL-RUNTIME_dev_manager ?= "udev"
-VIRTUAL-RUNTIME_login_manager ?= "tinylogin"
+VIRTUAL-RUNTIME_login_manager ?= "busybox"
 VIRTUAL-RUNTIME_init_manager ?= "sysvinit"
 VIRTUAL-RUNTIME_initscripts ?= "initscripts"
 VIRTUAL-RUNTIME_keymaps ?= "keymaps"
Comment 3 Jonathan Liu 2013-05-23 09:12:10 UTC
I am using the following patch for defconfig:

--- a/meta/recipes-core/busybox/busybox-1.20.2/defconfig  2013-04-28 21:54:32.703866579 -0700
+++ b/meta/recipes-core/busybox/busybox-1.20.2/defconfig 2013-05-20 00:16:07.908418249 -0700
@@ -449,39 +449,39 @@
 #
 # CONFIG_ADD_SHELL is not set
 # CONFIG_REMOVE_SHELL is not set
-# CONFIG_FEATURE_SHADOWPASSWDS is not set
+CONFIG_FEATURE_SHADOWPASSWDS=y
 # CONFIG_USE_BB_PWD_GRP is not set
 # CONFIG_USE_BB_SHADOW is not set
 CONFIG_USE_BB_CRYPT=y
 # CONFIG_USE_BB_CRYPT_SHA is not set
-# CONFIG_ADDUSER is not set
-# CONFIG_FEATURE_ADDUSER_LONG_OPTIONS is not set
+CONFIG_ADDUSER=y
+CONFIG_FEATURE_ADDUSER_LONG_OPTIONS=y
 # CONFIG_FEATURE_CHECK_NAMES is not set
-CONFIG_FIRST_SYSTEM_ID=0
-CONFIG_LAST_SYSTEM_ID=0
-# CONFIG_ADDGROUP is not set
-# CONFIG_FEATURE_ADDGROUP_LONG_OPTIONS is not set
-# CONFIG_FEATURE_ADDUSER_TO_GROUP is not set
-# CONFIG_DELUSER is not set
-# CONFIG_DELGROUP is not set
-# CONFIG_FEATURE_DEL_USER_FROM_GROUP is not set
-# CONFIG_GETTY is not set
-# CONFIG_LOGIN is not set
+CONFIG_FIRST_SYSTEM_ID=100
+CONFIG_LAST_SYSTEM_ID=999
+CONFIG_ADDGROUP=y
+CONFIG_FEATURE_ADDGROUP_LONG_OPTIONS=y
+CONFIG_FEATURE_ADDUSER_TO_GROUP=y
+CONFIG_DELUSER=y
+CONFIG_DELGROUP=y
+CONFIG_FEATURE_DEL_USER_FROM_GROUP=y
+CONFIG_GETTY=y
+CONFIG_LOGIN=y
 # CONFIG_LOGIN_SESSION_AS_CHILD is not set
 # CONFIG_PAM is not set
-# CONFIG_LOGIN_SCRIPTS is not set
-# CONFIG_FEATURE_NOLOGIN is not set
-# CONFIG_FEATURE_SECURETTY is not set
-# CONFIG_PASSWD is not set
-# CONFIG_FEATURE_PASSWD_WEAK_CHECK is not set
+CONFIG_LOGIN_SCRIPTS=y
+CONFIG_FEATURE_NOLOGIN=y
+CONFIG_FEATURE_SECURETTY=y
+CONFIG_PASSWD=y
+CONFIG_FEATURE_PASSWD_WEAK_CHECK=y
 # CONFIG_CRYPTPW is not set
 # CONFIG_CHPASSWD is not set
-CONFIG_FEATURE_DEFAULT_PASSWD_ALGO=""
-# CONFIG_SU is not set
-# CONFIG_FEATURE_SU_SYSLOG is not set
-# CONFIG_FEATURE_SU_CHECKS_SHELLS is not set
-# CONFIG_SULOGIN is not set
-# CONFIG_VLOCK is not set
+CONFIG_FEATURE_DEFAULT_PASSWD_ALGO="des"
+CONFIG_SU=y
+CONFIG_FEATURE_SU_SYSLOG=y
+CONFIG_FEATURE_SU_CHECKS_SHELLS=y
+CONFIG_SULOGIN=y
+CONFIG_VLOCK=y

 #
 # Linux Ext2 FS Progs


After switching default login manager to busybox, I see the following files that were provided by tinylogin changed:
/bin/login -> /bin/login.shadow shadow 4.3.4.3-r13
/usr/bin/su shadow 4.3.4.3-r13
/bin/addgroup busybox-1.20.2-r8
/sbin/sulogin busybox-1.20.2-r8
/usr/bin/passwd -> /usr/bin/passwd.shadow shadow 4.3.4.3-r13
/bin/delgroup busybox-1.20.2-r8
/usr/bin/vlock busybox-1.20.2-r8
#/bin/tinylogin
#/sbin/getty.tinylogin
/sbin/getty -> /sbin/agetty util-linux-agetty 2.23-r0
/bin/deluser busybox-1.20.2-r8
/bin/adduser busybox-1.20.2-r8
Comment 4 Chen Qi 2013-05-23 09:25:09 UTC
Liu,

Thanks for your share.
You're right, the following commands were provided by tinylogin previously, so after the switch, they should be provided by busybox.
addgroup, adduser, delgroup, deluser, login, su, getty, sulogin, passwd and vlock.

This is the basic line when I try to modify the defconfig file. 
And in addition, I want to enable as few utilities as possible.
But I'm not sure if I'm missing anything. (I would really appreciate it if you could point it out.)

Thanks,
Chen Qi
Comment 5 Jonathan Liu 2013-05-23 09:48:57 UTC
I needed CONFIG_FEATURE_SHADOWPASSWDS=y otherwise sulogin wouldn't work (it reads /etc/passwd but not /etc/shadow). I haven't tested your config yet but we would need to get it to prefer the busybox utilities over shadow and util-linux-agetty first to test it properly.
Comment 6 Chen Qi 2013-06-04 02:50:57 UTC
Hi all,

Below is a second design of this feature. Take a look at it if convenient, and any comment or suggestion will be appreciated.

Design:
1) Add a configuration fragment to the SRC_URI. The configuration fragment will be called login-utility.cfg, as its main purpose is to enable the login utility of busybox.

2) set the default login manager to 'busybox' instead of 'tinylogin'

3) Add a variable, BUSYBOX_SPLIT_SUID, to control whether to split the busybox binary into two parts, one with suid and the other not. We default it to "1" to enable the splitting, but users could still override it to disable the splitting. After all, busybox has no internal support for this suid apps splitting, so there might be users out there who trust busybox and want just one busybox binary.

4) Add a configuration file, busybox_suid_config_list, to control which applications should be split into the suid binary. This list is first derived from the command below. I choose to use a configuration file instead of hardcode the list, because I think it's possible that I might miss something, and in case of this situation, we could easily add the missing config item to the list.
for i in `grep -E "APPLET.*BB_SUID_((MAYBE|REQUIRE))" include/applets.h | grep -v _BB_SUID_DROP | cut -f 3 -d '(' | cut -f 1 -d ','`; do grep -i -E "config_(feature_|)$i(_| )" .config; done | cut -d' ' -f2

5) Busybox supports building individual apps, so the change above should still support this feature. So I marked this bug as dependent on bug#4570.


Others Notes:
The trick here is to build busybox twice with correct config items with each build. Please look at the proof-of-concept patch below, which mainly implements the 3rd and 4th parts of the design stated above.

diff --git a/meta/recipes-core/busybox/busybox.inc b/meta/recipes-core/busybox/busybox.inc
index 6abe167..50b52e3 100644
--- a/meta/recipes-core/busybox/busybox.inc
+++ b/meta/recipes-core/busybox/busybox.inc
@@ -12,6 +12,9 @@ LIC_FILES_CHKSUM = "file://LICENSE;md5=de10de48642ab74318e893a61105afbb"
 
 SECTION = "base"
 
+# Whether to split the suid apps into a seperate binary
+BUSYBOX_SPLIT_SUID ?= "1"
+
 export EXTRA_CFLAGS = "${CFLAGS}"
 export EXTRA_LDFLAGS = "${LDFLAGS}"
 
@@ -136,19 +139,52 @@ do_configure () {
 
 do_compile() {
 	unset CFLAGS CPPFLAGS CXXFLAGS LDFLAGS
-	oe_runmake busybox_unstripped
-	cp busybox_unstripped busybox
+	if [ "${BUSYBOX_SPLIT_SUID}" = "1" -a x`grep "CONFIG_FEATURE_INDIVIDUAL=y" .config` = x ]; then
+	# split the .config into two parts, and make two busybox binaries
+		cp .config .config.orig
+		oe_runmake allnoconfig
+		cp .config .config.allno
+		for item in `grep 'CONFIG_' ${WORKDIR}/busybox_suid_config_list`; do
+			echo "# $item is not set" >> .config.nosuid.tmp
+			grep -w "$item" .config.orig >> .config.suid.tmp
+		done
+		merge_config.sh -m .config.orig .config.nosuid.tmp
+		cp .config .config.nosuid
+		merge_config.sh -m .config.allno .config.suid.tmp
+		cp .config .config.suid
+
+		# compile with no suid apps
+		cp .config.nosuid .config
+		oe_runmake busybox_unstripped
+		cp busybox_unstripped busybox.nosuid
+		oe_runmake busybox.links
+		cp busybox.links busybox.links.nosuid
+
+		# compile with suid apps
+		cp .config.suid .config
+		oe_runmake busybox_unstripped
+		cp busybox_unstripped busybox.suid
+		oe_runmake busybox.links
+		cp busybox.links busybox.links.suid
+
+		# copy .config.orig back to .config, because the install process may check this file
+		cp .config.orig .config
+
+		# cleanup
+		rm .config.orig .config.nosuid.tmp .config.allno .config.suid.tmp
+	else
+		oe_runmake busybox_unstripped
+		cp busybox_unstripped busybox
+		oe_runmake busybox.links
+	fi	
 }
 
 do_install () {
-	oe_runmake busybox.links
 	if [ "${prefix}" != "/usr" ]; then
-		sed "s:^/usr/:${prefix}/:" busybox.links > busybox.links.new
-		mv busybox.links.new busybox.links
+		sed -i "s:^/usr/:${prefix}/:" busybox.links*
 	fi
 	if [ "${base_sbindir}" != "/sbin" ]; then
-		sed "s:^/sbin/:${base_sbindir}/:" busybox.links > busybox.links.new
-		mv busybox.links.new busybox.links
+		sed "s:^/sbin/:${base_sbindir}/:" busybox.links*
 	fi
 
 	install -d ${D}${sysconfdir}/init.d
@@ -157,12 +193,21 @@ do_install () {
 		# Install /bin/busybox, and the /bin/sh link so the postinst script
 		# can run. Let update-alternatives handle the rest.
 		install -d ${D}${base_bindir}
-		if grep -q "CONFIG_FEATURE_SUID=y" ${B}/.config; then
-			install -m 4755 ${B}/busybox ${D}${base_bindir}
+		if [ "${BUSYBOX_SPLIT_SUID}" = "1" ]; then
+			install -m 4755 ${B}/busybox.suid ${D}${base_bindir}
+			install -m 0755 ${B}/busybox.nosuid ${D}${base_bindir}
+			install -m 0644 ${S}/busybox.links.suid ${D}${sysconfdir}
+			install -m 0644 ${S}/busybox.links.nosuid ${D}${sysconfdir}
+			ln -sf busybox.nosuid ${D}${base_bindir}/sh
 		else
-			install -m 0755 ${B}/busybox ${D}${base_bindir}
+			if grep -q "CONFIG_FEATURE_SUID=y" ${B}/.config; then
+				install -m 4755 ${B}/busybox ${D}${base_bindir}
+			else
+				install -m 0755 ${B}/busybox ${D}${base_bindir}
+			fi
+			install -m 0644 ${S}/busybox.links ${D}${sysconfdir}
+			ln -sf busybox ${D}${base_bindir}/sh
 		fi
-		ln -sf busybox ${D}${base_bindir}/sh
 	else
 		install -d ${D}${base_bindir} ${D}${base_sbindir}
 		install -d ${D}${libdir} ${D}${bindir} ${D}${sbindir}
@@ -181,6 +226,7 @@ do_install () {
 		if [ -f ${D}/linuxrc.${BPN} ]; then
 			mv ${D}/linuxrc.${BPN} ${D}/linuxrc
 		fi
+
 	fi
 
 	if grep -q "CONFIG_SYSLOGD=y" ${B}/.config; then
@@ -217,7 +263,6 @@ do_install () {
                        install -m 644 ${WORKDIR}/mdev.conf ${D}${sysconfdir}/mdev.conf
                fi
 	fi
-	install -m 0644 ${S}/busybox.links ${D}${sysconfdir}
 
     if ${@base_contains('DISTRO_FEATURES','systemd','true','false',d)}; then
         install -d ${D}${systemd_unitdir}/system
@@ -249,22 +294,26 @@ python do_package_prepend () {
     dvar = d.getVar('D', True)
     pn = d.getVar('PN', True)
 
-    if os.path.exists('%s/bin/busybox' % (dvar)):
-        d.setVar('ALTERNATIVE_TARGET', "/bin/busybox")
-
-    f = open('%s/etc/busybox.links' % (dvar), 'r')
-
-    for alt_link_name in f:
-        alt_link_name = alt_link_name.strip()
-        alt_name = os.path.basename(alt_link_name)
-
-        # Match coreutils
-        if alt_name == '[':
-            alt_name = 'lbracket'
-
-        d.appendVar('ALTERNATIVE_%s' % (pn), ' ' + alt_name)
-        d.setVarFlag('ALTERNATIVE_LINK_NAME', alt_name, alt_link_name)
-    f.close()
+    def set_alternative_vars(links, target):
+        f = open('%s%s' % (dvar, links), 'r')
+        for alt_link_name in f:
+            alt_link_name = alt_link_name.strip()
+            alt_name = os.path.basename(alt_link_name)
+            # Match coreutils
+            if alt_name == '[':
+                alt_name = 'lbracket'
+            d.appendVar('ALTERNATIVE_%s' % (pn), ' ' + alt_name)
+            d.setVarFlag('ALTERNATIVE_LINK_NAME', alt_name, alt_link_name)
+            if os.path.exists('%s%s' % (dvar, target)):
+                d.setVarFlag('ALTERNATIVE_TARGET', alt_name, target)
+        f.close()
+        return
+
+    if d.getVar('BUSYBOX_SPLIT_SUID', True) != "1":
+        set_alternative_vars("/etc/busybox.links", "/bin/busybox")
+    else:
+        set_alternative_vars("/etc/busybox.links.nosuid", "/bin/busybox.nosuid")
+        set_alternative_vars("/etc/busybox.links.suid", "/bin/busybox.suid")
 }
 
 pkg_prerm_${PN} () {
diff --git a/meta/recipes-core/busybox/busybox_1.20.2.bb b/meta/recipes-core/busybox/busybox_1.20.2.bb
index 1445a5e..2e0f050 100644
--- a/meta/recipes-core/busybox/busybox_1.20.2.bb
+++ b/meta/recipes-core/busybox/busybox_1.20.2.bb
@@ -34,7 +34,8 @@ SRC_URI = "http://www.busybox.net/downloads/busybox-${PV}.tar.bz2;name=tarball \
            file://strict-atime.patch \
            file://fail_on_no_media.patch \
            file://inetd.conf \
-           file://inetd"
+           file://inetd \
+           file://busybox_suid_config_list"
 
 SRC_URI[tarball.md5sum] = "e025414bc6cd79579cc7a32a45d3ae1c"
 SRC_URI[tarball.sha256sum] = "eb13ff01dae5618ead2ef6f92ba879e9e0390f9583bd545d8789d27cf39b6882"
Comment 7 Chen Qi 2013-06-04 02:57:32 UTC
Hi Saul,

I did some tests with the above implementation. 
-- BUSYBOX_SPLIT_SUID enabled
-- BUSYBOX_SPLIT_SUID disabled
-- CONFIG_FEATURE_INDIVIDUAL enabled and disabled

Everything is OK for now and the size is a *little* smaller than that of tiny-login based system.

       6324    tmp/work/qemux86-poky-linux/core-image-minimal/1.0-r0/rootfs/ [tinylogin]

       6320    tmp/work/qemux86-poky-linux/core-image-minimal/1.0-r0/rootfs/ [busybox BUSYBOX_SPLIT_SUID enabled]

       6288    tmp/work/qemux86-poky-linux/core-image-minimal/1.0-r0/rootfs/ [busybox BUSYBOX_SPLIT_SUID disabled]

//Chen Qi
Comment 8 Chen Qi 2013-06-26 03:13:51 UTC
merged into poky/master: 977fb9a916c1f2f7cb5ceba4db89d2e0155b37a1
Comment 9 Mihai Lindner 2013-07-29 09:12:55 UTC
Before:
root@qemux86:~# ls -la `readlink -f /sbin/getty`
-rwsr-xr-x 1 root root 50540 Jul 22 10:35 /bin/tinylogin

After:
(commit 67864ca79da08df752487a3a4e1a975546da123d)
root@qemux86:~# ls -la `readlink -f /sbin/getty`
-rwxr-xr-x 1 root root 504656 Jul 22 18:24 /bin/busybox.nosuid