Re: can you make a hard disk read-only?

From: Nathan (no_at_where.com)
Date: 10/25/03


Date: Sat, 25 Oct 2003 04:31:54 GMT


"Dale Pontius" <dale@edgehp.invalid> wrote in message
news:od32nb.236.ln@tomcat.edgehp.invalid...
> In article <yy0ib.69969$9l5.8209@pd7tw2no>,
> Pardon me for responding to a grandchild, especially when the relevant
> text is missing, but...

Ahh, that's okay.

>
> In your response to me, you talked about using something with init to
> get capabilities back. Can you give me more details of what you did,
> and how you did it. I've heard that this can be done, but nothing about
> how to do it. When I "seal" my server, it won't even cleanly reboot,
> since I take away that capability, too. To get a reboot I have to be
> physically present at the keyboard or reset button. That's not bad,
> but log rotation doesn't work without delaying through a reboot, either.
> It would be nice to either automate, or at least keep my uptime up
> there.

Sure. I actually found a patch for init which does this. Apparently it was
written by Lamont Granquist. I got it to apply to sysvinit 2.84.
Basically, the patch adds a command called capset which asks for a password
and sends it to init. When init boots up, it reads an MD5 encrypted
password from /etc/capabilities.conf. Then, when init recieves the data
from capset, it checks the password, and if okay writes the new capabilites
to /proc/sys/kernel/cap-bound, which allows the given caps for new
processes. I included the patch below, hopefully line wrapping won't screw
it up. /etc/capabilities.conf should have just one line with the text
"cryptpass", a space, and then the MD5 hash like you'd find in /etc/shadow.

I've actually made a few other enhancements to the system too. Most of the
time / is mounted read-only, and /var is read-write, with some particular
files in /var marked as immutable. I patched the kernel to add 2 new
capabilities, one to disable opening of block devices and the other to
disable opening /etc/capabilities.conf. I believe that by disabling many
capabilities, I've pretty much made my system files 99.999% read-only. The
only way to change something would be to know the capabilities password or
find a bug in init or the kernel. One of these days, I'd like to post all
of the details about what I did and get some feedback on it...

The only major problem that I'm left with is that because I drop
capabilities in rc.local, which is executed after all daemons are started,
daemons still have full capabilities. I have no clue how to go about fixing
this.

It seems to me like kernel capabilites is a pretty powerful and useful tool
to secure a system, but I'm pretty disappointed that there are not a whole
lot of utilities to help utilize this power.

Nathan

--- The patch ---

diff -urN sysvinit-2.78-orig/src/Makefile sysvinit-2.78/src/Makefile
--- sysvinit-2.78-orig/src/Makefile Tue May 2 10:13:08 2000
+++ sysvinit-2.78/src/Makefile Tue May 2 15:55:05 2000
@@ -15,10 +15,10 @@

 # For Debian we do not build all programs, otherwise we do.
 ifeq ($(DEBIAN),)
-PROGS = init halt shutdown killall5 runlevel sulogin utmpdump \
+PROGS = init halt shutdown killall5 runlevel sulogin utmpdump capset\
   last mesg wall
 else
-PROGS = init halt shutdown killall5 runlevel sulogin last mesg
+PROGS = init halt shutdown killall5 runlevel sulogin last mesg capset
 endif

 BIN_OWNER = root
@@ -33,8 +33,8 @@

 all: $(PROGS)

-init: init.o init_utmp.o
- $(CC) $(LDFLAGS) $(STATIC) -o $@ init.o init_utmp.o
+init: init.o init_utmp.o md5_broken.o md5_crypt_broken.o
+ $(CC) $(LDFLAGS) $(STATIC) -o $@ init.o init_utmp.o md5_broken.o
md5_crypt_broken.o

 halt: halt.o ifdown.o utmp.o reboot.h
   $(CC) $(LDFLAGS) -o $@ halt.o ifdown.o utmp.o
@@ -54,6 +54,9 @@
 sulogin: sulogin.o md5_broken.o md5_crypt_broken.o
   $(CC) $(LDFLAGS) $(STATIC) -o $@ $^ $(LCRYPT)

+capset: capset.o
+ $(CC) $(LDFLAGS) -o $@ capset.o
+
 wall: dowall.o wall.o
   $(CC) $(LDFLAGS) -o $@ dowall.o wall.o

@@ -90,7 +93,7 @@
 distclean: clobber

 install:
- $(INSTALL) -m 755 halt init killall5 sulogin \
+ $(INSTALL) -m 755 halt init killall5 sulogin capset \
    runlevel shutdown $(ROOT)/sbin
   # These are not installed by default
 ifeq ($(DEBIAN),)
diff -urN sysvinit-2.78-orig/src/capset.c sysvinit-2.78/src/capset.c
--- sysvinit-2.78-orig/src/capset.c Wed Dec 31 16:00:00 1969
+++ sysvinit-2.78/src/capset.c Tue May 2 15:55:05 2000
@@ -0,0 +1,75 @@
+
+#include <stdio.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <termios.h>
+#include <asm/param.h> /* MAXHOSTNAMELEN */
+
+#include "initreq.h"
+
+int main(int argc, char *argv[]) {
+ int pipe_fd;
+ struct stat st;
+ struct init_request request;
+ struct termios old, tty;
+ int i;
+
+ if (argc != 2) {
+ fprintf(stderr, "usage: capset [new hex value]\n");
+ exit(-1);
+ }
+
+ sscanf(argv[1], "%x", &request.pid);
+
+ if ((pipe_fd = open(INIT_FIFO, O_RDWR|O_NONBLOCK)) < 0) {
+ perror("can't open /dev/initctl");
+ exit(-1);
+ }
+
+ fstat(pipe_fd, &st);
+
+ if (!S_ISFIFO(st.st_mode)) {
+ fprintf(stderr, "/dev/initctl is not a FIFO\n");
+ exit(-1);
+ }
+
+ printf("Enter Pass-Phrase: ");
+ fflush(stdout);
+ tcgetattr(0, &old);
+ tcgetattr(0, &tty);
+ tty.c_iflag &= ~(IUCLC|IXON|IXOFF|IXANY);
+ tty.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|TOSTOP);
+ tcsetattr(0, TCSANOW, &tty);
+
+ if (read(0, request.exec_name, sizeof(request.exec_name) +
sizeof(request.reserved) - 1) <= 0) {
+ fprintf(stderr, "can't read password\n");
+ exit(-1);
+ }
+
+ tcsetattr(0, TCSANOW, &old);
+ printf("\n");
+
+ for(i = 0; i < (sizeof(request.exec_name) + sizeof(request.reserved) - 1)
&& request.exec_name[i]; i++)
+ if (request.exec_name[i] == '\r' || request.exec_name[i] == '\n') {
+ request.exec_name[i] = 0;
+ break;
+ }
+
+ printf("capset: setting capabilities bounding set to 0x%08x\n",
+ request.pid);
+
+ request.magic = INIT_MAGIC;
+ request.cmd = INIT_CMD_CAPBOUND;
+
+ if (write(pipe_fd, &request, sizeof(request)) != sizeof(request)) {
+ perror("writing to /dev/initctl");
+ exit(-1);
+ }
+
+ exit(0);
+
+}
+
diff -urN sysvinit-2.78-orig/src/init.c sysvinit-2.78/src/init.c
--- sysvinit-2.78-orig/src/init.c Fri Feb 11 03:17:02 2000
+++ sysvinit-2.78/src/init.c Tue May 2 15:59:07 2000
@@ -23,6 +23,12 @@
  * did_boot made global and added to state - thanks, Miquel.
  * Yet another file descriptors leak - close state pipe if
  * re_exec fails.
+ *
+ * Modified: 2 May 2000, Lamont Granquist:
+ * Added /etc/capabilities.conf password and command passed
+ * through /dev/initctl to add capabilities to the capability
+ * bounding set in a running kernel.
+ *
  */

 #include <sys/types.h>
@@ -65,6 +71,7 @@
 #include "paths.h"
 #include "reboot.h"
 #include "set.h"
+#include "md5.h"

 #ifndef SIGPWR
 # define SIGPWR SIGUSR2
@@ -188,6 +195,91 @@
 };

 /*
+ * Procedures to impliment adding capabilities to the capability
+ * bounding set using crypted password set in /etc/capabilities.conf
+ *
+ */
+
+char md5password[256];
+
+int read_password() { /* read password from capabilities.conf file */
+ FILE *cap_conf_fp;
+ char buffer[256];
+
+ md5password[0] = '\0';
+
+ /* FIXME: should check that /etc/capabilities is immutable */
+
+ if ((cap_conf_fp = fopen("/etc/capabilities.conf", "r")) == NULL) {
+ log(L_VB, "no /etc/capabilities.conf, capability password disabled");
+ return(0);
+ }
+
+ while(fgets(buffer, 256, cap_conf_fp) != NULL) {
+ if(strncmp(buffer, "cryptpass", 9) == 0) {
+ int i;
+
+ if (!isspace(buffer[9])) {
+ log(L_VB, "bad cryptpass line in /etc/capabilities.conf");
+ continue;
+ }
+ i = 10; /* strlen("cryptpass") + 1 */
+ while(isspace(buffer[i]) && (i < 200)) i++;
+ if ((buffer[i] != '\0') && i != 200) {
+ int j = i;
+
+ while(!isspace(buffer[j]) && (buffer[j] != '\0')) j++;
+ if (buffer[j] == '\0') {
+ log(L_VB, "bad cryptpass in /etc/capabilities.conf");
+ continue;
+ }
+
+ strncpy(md5password, &buffer[i], j-i);
+ md5password[j-i+1] = '\0';
+/* log(L_VB, "crypt password set to %s", md5password); */
+
+ /* FIXME: validate crypt password */
+ break;
+ } else {
+ log(L_VB, "bad cryptpass in /etc/capabilities.conf");
+ }
+ }
+ }
+
+ fclose(cap_conf_fp);
+ return(0);
+
+}
+
+int password_ok(char *password) { /* uses last 256 bytes of request pkt */
+ password[255] = '\0'; /* force null termination */
+ if (strcmp(Brokencrypt_md5(password, md5password), md5password) == 0)
+ return(1);
+ return(0);
+}
+
+int set_cap_bound(int parm) {
+ int cap_fd;
+ char out[32];
+
+ /* FIXME: check that /proc/sys/kernel/cap-bound exists */
+
+ log(L_VB, "setting capability bounding set to 0x%08x", parm);
+ if ((cap_fd = open("/proc/sys/kernel/cap-bound", O_RDWR)) == -1) {
+ log(L_VB, "cannot open /proc/sys/kernel/cap-bound");
+ return(0);
+ }
+ snprintf(out, 32, "0x%x", parm);
+ if (write(cap_fd, out, strlen(out)) != strlen(out)) {
+ log(L_VB, "can't write to /proc/sys/kernel/cap-bound");
+ return(0);
+ }
+ close(cap_fd);
+ return(1);
+}
+
+
+/*
  * Sleep a number of seconds.
  *
  * This only works correctly because the linux select updates
@@ -2043,6 +2135,17 @@
    do_power_fail('O');
    quit = 1;
    break;
+ case INIT_CMD_CAPBOUND:
+ if (password_ok(request.exec_name)) {
+ if(set_cap_bound(request.pid)) {
+ log(L_VB, "capability set");
+ } else {
+ log(L_VB, "capability not set");
+ }
+ } else {
+ log(L_VB, "got bad password.");
+ }
+ break;
   default:
    log(L_VB, "got unimplemented initrequest.");
    break;
@@ -2374,7 +2477,8 @@
   */
    runlevel = '#';
    read_inittab();
-
+ read_password();
+
   } else {
  /*
   * Restart: unblock signals and let the show go on
@@ -2563,3 +2667,4 @@
   exit(0);
 #endif
 }
+
diff -urN sysvinit-2.78-orig/src/initreq.h sysvinit-2.78/src/initreq.h
--- sysvinit-2.78-orig/src/initreq.h Tue Jan 2 10:22:06 1996
+++ sysvinit-2.78/src/initreq.h Tue May 2 15:59:52 2000
@@ -22,6 +22,7 @@
 #define INIT_CMD_POWERFAIL 2
 #define INIT_CMD_POWERFAILNOW 3
 #define INIT_CMD_POWEROK 4
+#define INIT_CMD_CAPBOUND 5

 struct init_request {
   int magic; /* Magic number */
@@ -37,5 +38,8 @@
   char exec_name[128]; /* Program to execute */
   char reserved[128]; /* For future expansion. */
 };
+
+/* WARNING: INIT_CMD_CAPBOUND uses both exec_name[] and reserved[] as
+ one big char buffer */

 #endif



Relevant Pages