Tencent/tinker · error · IOException

Rest bytes insufficient, expect to read {} bytes but only {}

Error message

Rest bytes insufficient, expect to read {} bytes but only {} bytes were read.

What it means

ShareElfFile.readUntilLimit reads exactly bufferOut.limit() bytes from a FileChannel; if the channel returns fewer bytes (including -1 for EOF) it throws IOException with the supplied message plus 'Rest bytes insufficient, expect to read X bytes but only Y bytes were read.'. Within Tinker this is used while parsing ELF files (.so libraries) and OAT/odex headers, so the throw means the file on disk is shorter than the ELF/OAT structure claims — a truncated file.

Source

Thrown at tinker-android/tinker-android-loader-no-op/src/main/java/com/tencent/tinker/loader/shareutil/ShareElfFile.java:116

            } else {
                return FILE_TYPE_OTHERS;
            }
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (Throwable thr) {
                    // Ignored.
                }
            }
        }
    }

    public static void readUntilLimit(FileChannel channel, ByteBuffer bufferOut, String errMsg) throws IOException {
        bufferOut.rewind();
        int bytesRead = channel.read(bufferOut);
        if (bytesRead != bufferOut.limit()) {
            throw new IOException(errMsg + " Rest bytes insufficient, expect to read "
                    + bufferOut.limit() + " bytes but only "
                    + bytesRead + " bytes were read.");
        }
        bufferOut.flip();
    }

    public static String readCString(ByteBuffer buffer) {
        final byte[] rawBuffer = buffer.array();
        int begin = buffer.position();
        while (buffer.hasRemaining() && rawBuffer[buffer.position()] != 0) {
            buffer.position(buffer.position() + 1);
        }
        // Move to the start of next cstring.
        buffer.position(buffer.position() + 1);
        return new String(rawBuffer, begin, buffer.position() - begin - 1, Charset.forName("ASCII"));
    }

    public FileChannel getChannel() {

View on GitHub (pinned to 1b7ea02c23)

Solutions

  1. cleanPatch() and re-apply the patch so all native libs restore intact.
  2. Validate patch file md5 before applying so corrupt downloads never install.
  3. Ensure sufficient free storage before patch apply.
  4. If parsing your own ELF files with ShareElfFile, verify file length against the header's e_shoff+e_shnum*e_shentsize before reading.

Example fix

// before
ShareElfFile elf = new ShareElfFile(soFile); // may throw mid-parse

// after: pre-check size plausibility
if (soFile.length() < ElfHeader.MIN_ELF_SIZE) {
    throw new IOException("truncated so file: " + soFile);
}
Defensive patterns

Strategy: validation

Validate before calling

if (file.length() < 52 /* minimum 64-bit ehdr */) {
    throw new IOException("file too small to be ELF: " + file);
}

Try / catch

try {
    elfFile = new ShareElfFile(soFile);
} catch (IOException e) {
    // truncated or corrupt binary: reject and re-download/cleanPatch
}

Prevention

When it happens

Trigger: Parsing a truncated .so from the patch lib directory or a truncated odex/oat file: the ELF header or section/program header extends past the actual file size. Also triggered when a non-ELF path points at a short file after the magic check passed by luck.

Common situations: Partially downloaded/applied patch leaving truncated .so files; disk-full during patch apply; odex file cut off after an interrupted dexopt; storage corruption on aging devices.

Related errors


AI-assisted analysis of Tencent/tinker@1b7ea02c23 (2026-08-14). Data as JSON: /api/errors/30af4734de786fe1. Report an issue: GitHub.