Latest Tweets

Patching a non-GPL 2.4.XX GNU/Linux Kernel binary module

The issue

This post concerns an old technical problem related to GNU/Linux Kernel branch 2.4.XX and a non-GPL binary LKM developed by Adaptec. At the time, we had to load that LKM in order to setup a GNU/Linux RedHat 9.0 distro on a RAID-1 hard disks box. We had got an Adaptec AA1210 RAID SATA-1 controller, and there was no open source module available to use it out there, not to mention inside its own mainstream. Thus, the only LKM we could use at the time was a non-GPL, pre-compiled module developed by Adaptec. There was no sources, just the binary. In fact, there were a lot of different binaries, available for a lot of different RedHat versions and kernels but, unluckily, our GNU/Linux Kernel version was patched so as to use Open-MOSIX, this way there was nothing we could do to load it – apparently 😉 -.

CRCs

The 2.4 GNU/Linux kernel branch generates a CRC for any exported symbol. This CRC consists on eight hexadecimal numbers, and it looks like:

pci_find_device_Rsmp_c584f4e3

In this example, the exported symbol is pci_find_device(), and the CRC is c584f4e3.

The CRC is always generated during the GNU/Linux Kernel compilation process, and it strongly depends on the source code files. The tool in charge of generating this CRC is genksyms. If, for example, we make some changes on any of the GNU/Linux Kernel source files, as soon as we re-compile it this CRC could be completely different, not to mention when it comes to a next version branch, as to speak: 2.4.18 to 2.4.22.

Any GNU/Linux kernel binary module needs to call any exported symbol it is in need of, and so as to do that, this LKM looks for those symbols using their names and CRC numbers. Clearly, when an LKM needs to call pci_find_device(), it calls, as a matter of fact, some pci_find_device_R[SMP]_CRC symbol. This way, it is not feasible to use a pre-compiled kernel module on a running kernel in case there are some code changes like, say, a patched Kernel. For example, when it comes to Open MOSIX ones.

When this happens, an “unknown symbol” message appears throught dmesg and that LKM, simply, does not load at all.

It’s time to patch!

At the time, I was so frustrated. There were some little changes between the original Kernel version that Adaptec binary module was compiled for and our GNU/Linux Open-Mosix patched kernel. In fact, it was practically the same. I was fully determined to load it, and I was thinking the only chance I had was to patch those CRCs the LKM was looking for. All I needed to do was to get our original kernel sources for that OpenMOSIX and kernel version, try to get its CRCs, and then look for them inside the LKM object file so as to patch them all.

It was hard to accomplish but, hey! Wait a minute: I could do that using C and bash! It was really helpful to do this way.

I read the LKM object file using hexedit. It seemed quite easy to patch it using this tool, directly. There were a complete list of unknown symbols thanks to dmesg command, so it was a piece of cake to figure out what kind of symbols I was in need of patching.

I wrote a trivial shell script called “patch.sh” in charge of looking for those unknown symbols, writing down a complete list on an external ascii file. That shell script, developed using bash shell, looks like:

OBJDUMP=/usr/bin/objdump		# Ubicació del binari objdump
NEW_KERNEL=/usr/src/linux		# Fonts del nou kernel (¡¡compil.lat!!)
patcher=/usr/local/bin/patch	# Ruta del binari patch
 
CRC_CHECK=_R\?[a-f0-9]\{8\}
 
clear
 
#
# Trivialitats: comprobar arguments i presència del mòdul:
#
if [ ! $# -eq 1 ]; then
	echo "Uso: ${0} modulo.o"
	exit 1
fi
 
if [ ! -r $1  ]; then
	echo "El modulo $1 no es legible."
	exit 2
else
	#
	# Assegurar-se que es de la rama 2.4.X
	#
	version=`/sbin/modinfo $1|grep -o "2\.4"`
	if [ -z "$version" ]; then
		echo "$MODULE no esta compilado para la rama 2.4.X"
		exit 3
	fi
fi
 
#
# Asegurarse que tenim el nou kernel compil.lat amb els CRCs:
#
echo ">> Comprobando kernel nuevo ... "
if [ ! -d ${NEW_KERNEL}/include/linux/modules/ ]; then
	echo -ne "\tError, falta la información de CRC del nuevo kernel.\n"
	exit 2
else
	#
	# Buscar los archivos .ver:
	#
	ls ${NEW_KERNEL}/include/linux/modules/*.ver >/dev/null
	if [ $? -eq 1 ]; then
		echo -ne "\tError, compilacion incompleta del kernel.\n"
		exit 3
	fi
fi
echo -ne "\t\a[CORRECTO!]\n"
 
MODULE=$1
FILE_SYMBOLS=./${MODULE}.syms
 
#
# Si existeix un fitxer patch previ esborrar :
#
if [ -w ./$MODULE.patch ]; then
	rm -f ./$MODULE.patch
fi
 
# Obtenir els símbols no definitis del mòdul i generar fitxer:
echo ">> Generando archivo de símbolos no definidos ... "
`${OBJDUMP} --syms ${MODULE}|grep "*UND*"|tr -s ' ' ' '|cut -d" " -f3 \
	> ${FILE_SYMBOLS}`
 
# Descartar els símbols sense CRC :
echo ">> Agrupando símbolos con CRC ..."
symbols=`cat ${FILE_SYMBOLS}|grep -i -E ${CRC_CHECK}`
 
#
# Símbol a símbol, obtenir el seu CRC, buscar el nou al kernel i comparar
# ambdos. Si son diferents, afegir símbol amb NOU CRC al fitxer de patch:
#
for iSymbol in ${symbols}
do
 
	strlen=`expr length ${iSymbol}`
	issmp=`echo ${iSymbol}|grep -i Rsmp`
	if [ ! -z $issmp ]; then
		noCRC=`expr ${strlen} - 14`
	else
		noCRC=`expr ${strlen} - 10`	# Sense smp ...
	fi
 
	# Desar el nom del símbol i el seu CRC ... :
	CRCa=`echo ${iSymbol:noCRC}`
	if [ ! -z ${issmp} ]; then
		CRC=${CRCa:6}
	else
		CRC=${CRCa:2}			# Sense smp ...
	fi
	nSymbol=${iSymbol:0:noCRC}	# Desar nom del símbol
	echo -ne "Símbolo: ${nSymbol} , CRC=${CRC}"
 
	#
	# Buscar aquest símbol al nou kernel
	#
	newSymbol=`grep -h -E "#define __ver_$nSymbol\\t*" $NEW_KERNEL/include/linux/modules/*.ver`
	i=0
	for string in $newSymbol
	do
		i=`expr $i + 1`
		# Obtenim el nou CRC ...
		if [ $i -eq 3 ]; then
			if [ "${string:0:4}" == "smp_" ]; then
				NEW_CRC=${string:4}		# smp_CRC
			else
				NEW_CRC=${string}
			fi
			# Comparar ...
			if [ "$CRC" == "$NEW_CRC" ]; then
				echo " : [COINCIDEN]"
			else
				echo " : [CRC ERROR: $NEW_CRC]"
				# Volcar a fitxer :
				echo -e "${nSymbol} ${CRC} ${NEW_CRC}" >> ./${MODULE}.patch
			fi
		fi
	done
done
 
#
# Aplicar el patch sobre el mòdul, generant: patched.o
#
if [ -r ${MODULE}.patch ]; then
	echo -ne "Aplicando parche sobre módulo ${MODULE} ... "
	${patcher} ./${MODULE}.patch ./${MODULE}
	if [ $? -eq 0 ]; then
		echo "[OK, modulo parcheado en ./patched.o]"
	else
		echo "[ERROR]"
	fi
fi
 
#
# Purgar el fitxer temporal de símbols :
#
rm -f ./${MODULE}.syms 2>/dev/null

At the same time, this shell script calls an external binary tool (patch), developed in C, which was really in charge of patching that non-GPL binary module using the previous unknown symbols list generated by patch.sh:

#include <stdio.h>
#include <sys/fcntl.h>				/* O_RDONLY	 */
#include <sys/stat.h>				/* fstat     */
#include <limits.h>					/* SSIZE_MAX */
#include <string.h>					/* memmem */
 
#define MAX_SYMBOL_SIZE	256
#define MAX_CRC_SIZE	9
#define MAX_LINE_SIZE	( MAX_SYMBOL_SIZE + MAX_CRC_SIZE )
 
const char *FORMAT = "%s %s %s", * buffer = NULL;
 
void patch_module (char *, char *, char *, int);
 
int main (int argc, char **argv){
 
	FILE *fpatch = NULL;
	char * symbol, * CRC, * NEW_CRC;
	int fmod, fout, bRead=0;
	struct stat fmod_stat;
 
#ifdef _DEBUG_
	printf("DEBUG_MSG: SSIZE_MAX: %d bytes.\n", SSIZE_MAX);
#endif
 
	if(argc!=3){
		printf("Uso: %s modulo.o.patch modulo.o\n",
				*argv);
		return -1;
	}
 
	/* Llegir el fitxer línea a línea */
	if((fpatch=fopen(*(argv+1),"r"))==NULL){
		perror("FOPEN"); return -2;
	}
 
	/* Obrir el mòdul binari : */
	if((fmod=open(*(argv+2),O_RDONLY))==-1){
		perror("OPEN"); return -3;
	}
 
	/* Obtenir tamany del fitxer */
	if(fstat(fmod,&fmod_stat)==-1){
		perror("FSTAT"); return -4;
	}
 
	/* Reservar el tamany del fitxer si es inferior o igual al màxim */
	if(fmod_stat.st_size<SSIZE_MAX ? fmod_stat.st_size : SSIZE_MAX))==-1)
	{
		perror("READ"); free(buffer); close(fmod); return -4;
	} close(fmod);
 
#ifdef _DEBUG_
	printf("DEBUG_MSG: Leidos %d bytes del modulo sobre %0xd.\n", bRead,
						buffer);
#endif
 
	/* Reservar memoria */
	symbol = (char *)malloc(sizeof(char)*MAX_SYMBOL_SIZE);
	CRC  = (char *)malloc(sizeof(char)*MAX_CRC_SIZE);
	NEW_CRC  = (char *)malloc(sizeof(char)*MAX_CRC_SIZE);
 
	/* Simbol a símbol, obtenir el CRC i passar-ho a patch_mod */
	while(!feof(fpatch)){
		memset(symbol,0,MAX_SYMBOL_SIZE-1);
		memset(CRC,0,MAX_CRC_SIZE-1);
		if(fscanf(fpatch,FORMAT,symbol,CRC,NEW_CRC)==EOF)break;
		patch_module(symbol,CRC,NEW_CRC,bRead);
	}
 
	/* Desar memoria a fitxer : */
	if((fout=open("patched.o",O_CREAT|O_TRUNC|O_WRONLY,
						S_IRWXU|S_IRGRP|S_IROTH))==-1)perror("OPEN");
	else {
		if((bRead=write(fout,buffer,bRead))==-1)perror("WRITE");
#ifdef _DEBUG_
		printf("DEBUG_MSG: Escritos %d bytes sobre patched.o.\n",bRead);
#endif
		close(fout);
	}
 
	// Purgar i netejar :
	free(symbol); free(CRC); free(NEW_CRC); free(buffer);
	fclose(fpatch);
 
	return 0;
}
 
//
// Busca el CRC antic dins del buffer del mòdul, i el substitueix pel
// nou CRC.
//
void patch_module (char * symbolName, char * oldCRC, char * newCRC,
					int bufferLength) {
 
	void * pCRC = NULL;
	char curCRC[9], * pBuffer = buffer;
	int iByte;
 
#ifdef _DEBUG_
	printf("Buscando CRC: %s en %0xd ... ", oldCRC, buffer);
#endif
	if((pCRC = memmem(buffer,bufferLength,oldCRC,8))==NULL) {
#ifdef _DEBUG_
		printf("[ERROR!!]");					// CRC no trobat !
#endif
	} else {
		memcpy(curCRC,pCRC,8); curCRC[8]='\0';
#ifdef _DEBUG_
		printf("OK, posicion: %0xd (%s).\n\tParcheando por %s ... ",
				pCRC, curCRC, newCRC);
#endif
		pBuffer = pCRC;							// Posicionarnos
		for(iByte=0;iByte<=7;iByte++)			// Aplicar els bytes nous
			*(pBuffer+iByte)=*(newCRC+iByte);
#ifdef _DEBUG_
		memcpy(curCRC,pCRC,8);
		printf("\n\tContenido de %0xd = %s", pCRC, curCRC);
#endif
	}
	printf("\n");
}

So, in order to be capable of loading that Adaptec aa1210.o LKM, I ran, as a root, on the command prompt:

./patch_mod aar1210.o

As a result …

The Adapatec AA1210 driver loaded perfectly well, and I was able to setup an Open-MOSIX patched Kernel at the very same time although  I had no aa1210.o sources at all!

Below, a screen-shot showing how the whole “patching” process looked like:

17

Links and references

You can get the sources of patch_mod, as it is named, here: patch_mod.tar.

Formerly posted on my old website: HERE.

Some obvious limitations

My patch_mod tool can patch any binary module in order to allow it to load and attach on a not so different GNU/Linux Kernel runtime version. At the same time, it seems quite clear that those patched CRC symbols must be, in essence, more or less the very same functions, as in our concrete case of patching aar1210.o LKM. I mean, our GNU/Linux kernel version was relatively different: in fact, all the symbols needed by aar1210.o were not different at all, just those in charge of adding Open-MOSIX functionality. When there could be some changes concerning symbols needed by a given patch-to-be module, this could be absolutely disastrous: maybe the module could be load, but the system could end up in a total crashing or Kernel PANIC or – if you’re lucky -, Kernel OOPS.