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:
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.