Linux - SecurityThis forum is for all security related questions.
Questions, tips, system compromises, firewalls, etc. are all included here.
Notices
Welcome to LinuxQuestions.org, a friendly and active Linux Community.
You are currently viewing LQ as a guest. By joining our community you will have the ability to post topics, receive our newsletter, use the advanced search, subscribe to threads and access many other special features. Registration is quick, simple and absolutely free. Join our community today!
Note that registered members see fewer ads, and ContentLink is completely disabled once you log in.
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..
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.
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
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
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!
LinuxQuestions.org is looking for people interested in writing
Editorials, Articles, Reviews, and more. If you'd like to contribute
content, let us know.