LinuxQuestions.org
Have you heard the LinuxQuestions.org Podcast?
Go Back   LinuxQuestions.org > Forums > Linux > Linux - Security
User Name
Password
Linux - Security This forum is for all security related questions.
Questions, tips, system compromises, firewalls, etc. are all included here.

Notices

Reply
 
Thread Tools
Old 10-25-2002, 10:01 AM   #1
markus1982
Senior Member
 
Registered: Aug 2002
Location: Stuttgart (Germany)
Distribution: Debian/GNU Linux
Posts: 1,467
Thanked: 0
chrooting mysql


[Log in to get rid of this advertisement]
Anybody chrooted MySQL already? I've been searching and it's a bit tricky to do so ... somebody can push me into the right direction?

Last edited by markus1982; 10-25-2002 at 10:03 AM..
markus1982 is offline     Reply With Quote
Old 10-25-2002, 11:12 PM   #2
unSpawn
Moderator
 
Registered: May 2001
Posts: 16,716
Blog Entries: 30
Thanked: 283
No, I can't, but can you share the docs you've found so far?
unSpawn is offline     Reply With Quote
Old 10-26-2002, 04:58 AM   #3
markus1982
Senior Member
 
Registered: Aug 2002
Location: Stuttgart (Germany)
Distribution: Debian/GNU Linux
Posts: 1,467
Thanked: 0

Original Poster
Honestly I didn't really find any useful documents for chrooting mysql. The --chroot parameter of mysqld also seems to be not setting up a real chrooted area ... like when the databases reside outside of the chrooted area it still can access it? That's why I rather rely on the chroot command of linux. For more information about --chroot parameter of mysqld check http://www.mysql.com/doc/en/Command-line_options.html


I've done most of the chrooting stuff so far, but for instance I would like to rely on safe_mysqld but for this to work you need the /bin/sh binary in the chrooted environment which I think of not really a secure chrooted area - what do you think about that?


What I've done so far is based on the Binary distribution of mysql, binary since the rpm's are not relocateable! Here's what I did so far:
Code:
#!/usr/bin/perl

# install MySQL chrooted
# base is the binary distribution of MySQL

system("chattr -i /etc/group /etc/gshadow /etc/passwd /etc/shadow");
system("groupadd mysql ; useradd -g mysql mysql -s /bin/false -d /server/mysql");
system("chattr +i /etc/group /etc/gshadow /etc/passwd /etc/shadow");
system("mkdir -p /server/mysql/tmp");
system("cp mysql-*-pc-linux-gnu-i686.tar.gz /server");
chdir("/server");
system("tar xvfz mysql-*-pc-linux-gnu-i686.tar.gz");
system("mv mysql-*-pc-linux-gnu-i686 mysql/binary");
chdir("/server/mysql/binary");

open(FHANDLE, "/server/mysql/binary/bin/mysqlaccess");
@mysqlaccess = <FHANDLE>;
close(FHANDLE);

for ($i = 0; $i < scalar(@mysqlaccess); $i++) {
	if (@mysqlaccess[$i] =~ /\$MYSQL/) {
		@mysqlaccess[$i] = "\t\$MYSQL     = '/server/mysql/binary/bin/mysql'\n";
		last;}
}



open(FHANDLE, "/server/mysql/binary/bin/mysqlaccess");
print FHANDLE @mysqlaccess;
close(FHANDLE);

system("mv data ../databases");
system("ln -s ../databases data");
system("./scripts/mysql_install_db");
system("chown -R root  /server/mysql/binary");
system("chown -R mysql /server/mysql/databases");
system("chgrp -R mysql /server/mysql/binary");
system("chmod 777 /server/mysql/tmp ; chattr +t /server/mysql/tmp");
system("ln -s /server/mysql/tmp/mysql.sock /tmp/mysql.sock");
What needs to be done:
  • create etc/passwd with just mysql user in it
  • create etc/my.cnf and configure it
  • chattr +i -R etc
  • create startup script which calls mysqld like this:
    chroot /server/mysql /binary/bin/mysqld --basedir=/binary/ --datadir=/databases/ -u mysql
  • add path of libmysqlclient.so to /etc/ld.so.conf so Perl modules have access to mysql


So most of it is done already ... just quitted with the rest since it was late. It works BTW and I run it chrooted already - I've tested that ... but things like safe_mysqld relying on the bash binary is just annoying ...

Last edited by markus1982; 10-26-2002 at 05:01 AM..
markus1982 is offline     Reply With Quote
Old 10-26-2002, 01:47 PM   #4
unSpawn
Moderator
 
Registered: May 2001
Posts: 16,716
Blog Entries: 30
Thanked: 283
Yes it seems mysql's --chroot ain't working the way it should unless you opt for doing chdir-before-chroot yourself. Now if you can find out why it specifically relies on Bash, like "gfind </mysqlchrootdir> "bash"" that would be helpfull.
I run several apps chrooted with the Grsecurity kernel patch (also in the LQ kernels) to make sure chrooting is always preceded by a chdir even if the app itself doesn't provide that, it also has many tweakable settings for what to allow inside of a chroot and the possibility to audit/log about *everything*. Grsecurity also takes care of the more devious aspects of protecting a chroot, like the double-chroot to get out of it and /dev/(k)mem access, won't let users access other users /proc stuff etc, etc. As a chroot shell I use Jail because it allows me to set up the basic skeleton for a jail relatively easy, so I only have to worry about tweaking like dir/file permissions and conf files. If I need to provide a login and shell, I'll use a modified Busybox binary with symlinks tweaked for that env, and sash if necessary.
Haven't found any probs with sh in itself inside a chrooted env, it's what you provide to go with it, like access to outside pipes and sockets (syslog), suid binaries, access to /proc, /dev/(k)mem and chroot, mknod binaries and the like. Check Escaping a chroot and using Chroot Securely to be (more) sure.
unSpawn is offline     Reply With Quote
Old 10-26-2002, 04:11 PM   #5
markus1982
Senior Member
 
Registered: Aug 2002
Location: Stuttgart (Germany)
Distribution: Debian/GNU Linux
Posts: 1,467
Thanked: 0

Original Poster
The only location I so far know of using /bin/sh is in the safe_mysqld part of the script, here's what the MySQL Documentation says about safe_mysqld:
Quote:
safe_mysqld is the recommended way to start a mysqld daemon on Unix. safe_mysqld adds some safety features such as restarting the server when an error occurs and logging run-time information to a log file.
So ideally safe_mysqld should be used instead calling it directly - which I currently have in mind since I don't want to use safe_mysqld cause of it's bash reguiring...
Code:
# safe_mysqld script

#!/bin/sh
# Copyright Abandoned 1996 TCX DataKonsult AB & Monty Program KB & Detron HB
# This file is public domain and comes with NO WARRANTY of any kind
#
# scripts to start the MySQL daemon and restart it if it dies unexpectedly
#
# This should be executed in the MySQL base directory if you are using a
# binary installation that has other paths than you are using.
#
# mysql.server works by first doing a cd to the base directory and from there
# executing safe_mysqld

trap '' 1 2 3 15			# we shouldn't let anyone kill us

defaults=
case "$1" in
    --no-defaults|--defaults-file=*|--defaults-extra-file=*)
      defaults="$1"; shift
      ;;
esac

parse_arguments() {
  # We only need to pass arguments through to the server if we don't
  # handle them here.  So, we collect unrecognized options (passed on
  # the command line) into the args variable.
  pick_args=$1; shift

  for arg do
    case "$arg" in
      # these get passed explicitly to mysqld
      --basedir=*) MY_BASEDIR_VERSION=`echo "$arg" | sed -e "s;--[^=]*=;;"` ;;
      --datadir=*) DATADIR=`echo "$arg" | sed -e "s;--[^=]*=;;"` ;;
      --pid-file=*) pid_file=`echo "$arg" | sed -e "s;--[^=]*=;;"` ;;
      --user=*)    user=`echo "$arg" | sed -e "s;--[^=]*=;;"` ; SET_USER=1 ;;

      # these two might have been set in a [safe_mysqld] section of my.cnf
      # they get passed via environment variables to safe_mysqld
      --socket=*)  MYSQL_UNIX_PORT=`echo "$arg" | sed -e "s;--[^=]*=;;"` ;;
      --port=*)    MYSQL_TCP_PORT=`echo "$arg" | sed -e "s;--[^=]*=;;"` ;;

      # safe_mysqld-specific options
      --ledir=*)   ledir=`echo "$arg" | sed -e "s;--[^=]*=;;"` ;;
      --err-log=*) err_log=`echo "$arg" | sed -e "s;--[^=]*=;;"` ;;
      # QQ The --open-files should be removed
      --open-files=*) open_files=`echo "$arg" | sed -e "s;--[^=]*=;;"` ;;
      --open-files-limit=*) open_files=`echo "$arg" | sed -e "s;--[^=]*=;;"` ;;
      --core-file-size=*) core_file_size=`echo "$arg" | sed -e "s;--[^=]*=;;"` ;;
      --timezone=*) TZ=`echo "$arg" | sed -e "s;--[^=]*=;;"` ; export TZ; ;;
      --mysqld=*)   MYSQLD=`echo "$arg" | sed -e "s;--[^=]*=;;"` ;;
      --mysqld-version=*)
	tmp=`echo "$arg" | sed -e "s;--[^=]*=;;"`
	if test -n "$tmp"
	then
	  MYSQLD="mysqld-$tmp"
	else
	  MYSQLD="mysqld"
	fi
	;;
      *)
        if test $pick_args -eq 1
        then
          # This sed command makes sure that any special chars are quoted,
          # so the arg gets passed exactly to the server.
          args="$args "`echo "$arg" | sed -e 's,\([^a-zA-Z0-9_.-]\),\\\\\1,g'`
        fi
        ;;
    esac
  done
}

MY_PWD=`pwd`
# Check if we are starting this relative (for the binary release)
if test -d $MY_PWD/data/mysql -a -f ./share/mysql/english/errmsg.sys -a \
 -x ./bin/mysqld
then
  MY_BASEDIR_VERSION=$MY_PWD		# Where bin, share and data are
  ledir=$MY_BASEDIR_VERSION/bin		# Where mysqld is
  DATADIR=$MY_BASEDIR_VERSION/data
  if test -z "$defaults"
  then
    defaults="--defaults-extra-file=$MY_BASEDIR_VERSION/data/my.cnf"
  fi
# Check if this is a 'moved install directory'
elif test -f ./var/mysql/db.frm -a -f ./share/mysql/english/errmsg.sys -a \
 -x ./libexec/mysqld
then
  MY_BASEDIR_VERSION=$MY_PWD		# Where libexec, share and var are
  ledir=$MY_BASEDIR_VERSION/libexec	# Where mysqld is
  DATADIR=$MY_BASEDIR_VERSION/var
else
  MY_BASEDIR_VERSION=/
  DATADIR=/var/lib/mysql
  ledir=/usr/sbin
fi

MYSQL_UNIX_PORT=${MYSQL_UNIX_PORT:-/var/lib/mysql/mysql.sock}
MYSQL_TCP_PORT=${MYSQL_TCP_PORT:-3306}
user=mysql

# Use the mysqld-max binary by default if the user doesn't specify a binary
if test -x $ledir/mysqld-max
then
  MYSQLD=mysqld-max
else
  MYSQLD=mysqld
fi

# these rely on $DATADIR by default, so we'll set them later on
pid_file=
err_log=
SET_USER=0

# Get first arguments from the my.cnf file, groups [mysqld] and [safe_mysqld]
# and then merge with the command line arguments
if test -x ./bin/my_print_defaults
then
  print_defaults="./bin/my_print_defaults"
elif test -x /usr/bin/my_print_defaults
then
  print_defaults="/usr/bin/my_print_defaults"
elif test -x /usr/bin/mysql_print_defaults
then
  print_defaults="/usr/bin/mysql_print_defaults"
else
  print_defaults="my_print_defaults"
fi

args=
parse_arguments 0 `$print_defaults $defaults mysqld server safe_mysqld`
parse_arguments 1 "$@"

if test ! -x $ledir/$MYSQLD
then
  echo "The file $ledir/$MYSQLD doesn't exist or is not executable"
  echo "Please do a cd to the mysql installation directory and restart"
  echo "this script from there as follows:"
  echo "./bin/safe_mysqld".
  exit 1
fi

if test -z "$pid_file"
then
  pid_file=$DATADIR/`/bin/hostname`.pid
else
  case "$pid_file" in
    /* ) ;;
    * )  pid_file="$DATADIR/$pid_file" ;;
  esac
fi
test -z "$err_log"  && err_log=$DATADIR/`/bin/hostname`.err

export MYSQL_UNIX_PORT
export MYSQL_TCP_PORT


NOHUP_NICENESS="nohup"
if test -w /
then
  NOHUP_NICENESS=`nohup nice 2>&1`
 if test $? -eq 0 && test x"$NOHUP_NICENESS" != x0 && nice --1 echo foo > /dev/null 2>&1
 then
    NOHUP_NICENESS="nice --$NOHUP_NICENESS nohup"
  else
    NOHUP_NICENESS="nohup"
  fi
fi

USER_OPTION=""
if test -w /
then
  if test "$user" != "root" -o $SET_USER = 1
  then
    USER_OPTION="--user=$user"
  fi
  # If we are root, change the err log to the right user.
  touch $err_log; chown $user $err_log
  if test -n "$open_files"
  then
    ulimit -n $open_files
  fi
  if test -n "$core_file_size"
  then
    ulimit -c $core_file_size
  fi
fi

#
# If there exists an old pid file, check if the daemon is already running
# Note: The switches to 'ps' may depend on your operating system
if test -f $pid_file
then
  PID=`cat $pid_file`
  if /bin/kill -0 $PID > /dev/null 2> /dev/null
  then
    if /bin/ps p $PID | grep mysqld > /dev/null
    then    # The pid contains a mysqld process
      echo "A mysqld process already exists"
      echo "A mysqld process already exists at " `date` >> $err_log
      exit 1
    fi
  fi
  rm -f $pid_file
  if test -f $pid_file
  then
    echo "Fatal error: Can't remove the pid file: $pid_file"
    echo "Fatal error: Can't remove the pid file: $pid_file at " `date` >> $err_log
    echo "Please remove it manually and start $0 again"
    echo "mysqld daemon not started"
    exit 1
  fi
fi

#
# Uncomment the following lines if you want all tables to be automaticly
# checked and repaired at start
#
# echo "Checking tables in $DATADIR"
# $MY_BASEDIR_VERSION/bin/myisamchk --silent --force --fast --medium-check -O key_buffer=64M -O sort_buffer=64M $DATADIR/*/*.MYI
# $MY_BASEDIR_VERSION/bin/isamchk --silent --force -O sort_buffer=64M $DATADIR/*/*.ISM

echo "Starting $MYSQLD daemon with databases from $DATADIR"

# Does this work on all systems?
#if type ulimit | grep "shell builtin" > /dev/null
#then
#  ulimit -n 256 > /dev/null 2>&1		# Fix for BSD and FreeBSD systems
#fi

echo "`date +'%y%m%d %H:%M:%S  mysqld started'`" >> $err_log
while true
do
  rm -f $MYSQL_UNIX_PORT $pid_file	# Some extra safety
  if test -z "$args"
  then
    $NOHUP_NICENESS $ledir/$MYSQLD $defaults --basedir=$MY_BASEDIR_VERSION --datadir=$DATADIR $USER_OPTION --pid-file=$pid_file --skip-locking >> $err_log 2>&1
  else
    eval "$NOHUP_NICENESS $ledir/$MYSQLD $defaults --basedir=$MY_BASEDIR_VERSION --datadir=$DATADIR $USER_OPTION --pid-file=$pid_file --skip-locking $args >> $err_log 2>&1"
  fi
  if test ! -f $pid_file		# This is removed if normal shutdown
  then
    break
  fi

  if true
  then
    # Test if one process was hanging.
    # This is only a fix for Linux (running as base 3 mysqld processes)
    # but should work for the rest of the servers.
    # The only thing is ps x => redhat 5 gives warnings when using ps -x.
    # kill -9 is used or the process won't react on the kill.
    numofproces=`ps xa | grep -v "grep" | grep -c $ledir/$MYSQLD`
    echo -e "\nNumber of processes running now: $numofproces" | tee -a $err_log
    I=1
    while test "$I" -le "$numofproces"
    do 
      PROC=`ps xa | grep $ledir/$MYSQLD | grep -v "grep" | sed -n '$p'` 
	for T in $PROC
	do
	  break
	done
	#    echo "TEST $I - $T **"
	if kill -9 $T
	then
	  echo "$MYSQLD process hanging, pid $T - killed" | tee -a $err_log
	else 
	  break
	fi
	I=`expr $I + 1`
    done
  fi

  echo "`date +'%y%m%d %H:%M:%S  mysqld restarted'`" | tee -a $err_log
done

echo "`date +'%y%m%d %H:%M:%S  mysqld ended'`" | tee -a $err_log
echo "" | tee -a $err_log
markus1982 is offline     Reply With Quote
Old 10-26-2002, 04:15 PM   #6
markus1982
Senior Member
 
Registered: Aug 2002
Location: Stuttgart (Germany)
Distribution: Debian/GNU Linux
Posts: 1,467
Thanked: 0

Original Poster
Quote:
As a chroot shell I use Jail because it allows me to set up the basic skeleton for a jail relatively easy, so I only have to worry about tweaking like dir/file permissions and conf files.
Unfortunately at the time I tried I couldn't access the homepage of Jail, anyways seems like I'm going to take a look at this one.

The other URL's you've been providing were helpful, have read those already though
markus1982 is offline     Reply With Quote
Old 10-29-2002, 10:22 AM   #7
markus1982
Senior Member
 
Registered: Aug 2002
Location: Stuttgart (Germany)
Distribution: Debian/GNU Linux
Posts: 1,467
Thanked: 0

Original Poster
Since the ps commands, etc rely on the /proc filesystem you can't run safe_mysqld in the chrooted area. So if you want to use safe_mysqld you HAVE to use it from outside of the chrooted proccess and have to MODIFY the safe_mysql script to make it work.


You may also test this using:
Code:
#!/usr/bin/perl

# install MySQL chrooted
# base is the binary distribution of MySQL

system("chattr -i /etc/group /etc/gshadow /etc/passwd /etc/shadow");
system("groupadd mysql ; useradd -g mysql mysql -m -k /server/mysql -s /bin/false -d /server/mysql");
system("chattr +i /etc/group /etc/gshadow /etc/passwd /etc/shadow");
system("mkdir -p /server/mysql/dev /server/mysql/etc /server/mysql/tmp");
system("cp mysql-*-pc-linux-gnu-i686.tar.gz /server");
chdir("/server");
system("tar xvfz mysql-*-pc-linux-gnu-i686.tar.gz");
system("mv mysql-*-pc-linux-gnu-i686 mysql/binary");
chdir("/server/mysql/binary");

open(FHANDLE, "/server/mysql/binary/bin/mysqlaccess");
@mysqlaccess = <FHANDLE>;
close(FHANDLE);

for ($i = 0; $i < scalar(@mysqlaccess); $i++) {
	if (@mysqlaccess[$i] =~ /\$MYSQL/) {
		@mysqlaccess[$i] = "\t\$MYSQL     = '/server/mysql/binary/bin/mysql'\n";
		last;}
}



open(FHANDLE, "/server/mysql/binary/bin/mysqlaccess");
print FHANDLE @mysqlaccess;
close(FHANDLE);

system("mv data ../databases");
system("ln -s ../databases data");
system("./scripts/mysql_install_db");
system("chown -R root  /server/mysql/binary");
system("chown -R mysql /server/mysql/databases");
system("chgrp -R mysql /server/mysql/binary");
system("chmod 777 /server/mysql/tmp ; chattr +t /server/mysql/tmp");
system("ln -s /server/mysql/tmp/mysql.sock /tmp/mysql.sock");
system("less /etc/passwd | grep mysql > /server/mysql/etc/passwd");
system("chmod 0444 /server/mysql/etc/passwd");
system("chattr +i -R /server/mysql/etc");

system("mknod /server/mysql/dev/null c 1 3 ; chattr +i -R /server/mysql/dev");

#	=================================================================
#    If you want to use safe_mysqld (and use it chrooted also):

@binaries_to_copy = (
			"/bin/bash",
			"/bin/cat",
			"/bin/chown",
			"/bin/date",
			"/bin/echo",
			"/bin/grep",
            "/bin/hostname",
			"/bin/nice",
			"/bin/ps",
			"/bin/rm",
			"/bin/sed",
            "/bin/touch",
			"/usr/bin/nohup",
            "/usr/bin/tee",
            "/usr/bin/test"
		);
        
for ($i = 0; $i < scalar(@binaries_to_copy); $i++) {
	system("ldd ".@binaries_to_copy[$i]." >> libraries.req");
}

open(FHANDLE, "libraries.req");
@libaries_required = <FHANDLE>;
close(FHANDLE);

unlink "libraries.req";

for ($i = 0; $i < scalar(@libaries_required); $i++) {
	@libaries_required[$i] =~/.+=>\s(.+)\s\(.+/;
    $library = $1;
    
	if ($libraries_to_copy !~ /$library/) {
		$libraries_to_copy .= $library." "; }
}

system("cp --parents ".join(" ", @binaries_to_copy)." /server/mysql");
system("cp --parents ".$libraries_to_copy." /server/mysql");
system("strip /server/mysql/lib/i686/* /server/mysql/lib/*");
chdir("/server/mysql/bin");
system("ln -s bash sh");


#	-----------------------------------------------------------------
#	How to run safe_mysqld also chrooted:
    
#	chroot /server/mysql
#    cd binary
#    ./bin/safe_mysqld --user=mysql &    
#	=================================================================

I will post the chroot-creation-script for MySQL here if interest once I finished it!
markus1982 is offline     Reply With Quote

Reply

Bookmarks


Thread Tools

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

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


Similar Threads
Thread Thread Starter Forum Replies Last Post
mysql error Can't connect to local MySQL server through socket '/var/lib/mysql/mysql. Dannux Linux - Software 3 03-24-2006 09:44 AM
chrooting nucleocide Linux - Software 2 08-08-2005 05:30 PM
FYI: mysql setup tutorial inl. chrooting markus1982 Linux - Security 0 03-07-2004 11:53 AM
chrooting daemons markus1982 Linux - Security 2 11-21-2002 12:04 PM
(short) HOWTO: chrooting MySQL markus1982 Linux - Security 5 11-16-2002 01:24 PM


All times are GMT -5. The time now is 03:46 AM.

Main Menu
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
RSS2  LQ Podcast
RSS2  LQ Radio
Twitter: @linuxquestions
identi.ca: @linuxquestions
Facebook: @linuxquestions
Open Source Consulting | Domain Registration