Skip to content

Truncated artifact download can be reported as a successful install #6

Description

@YeYinYing

Summary

KPM v0.2.2 can treat a truncated artifact download as successful and continue into installation. In the observed case, the UI eventually printed Installed 1 package(s) successfully, but KOReader was not installed and no installed_packages row was created.

Environment

  • Device: Kindle Paperwhite 3 (7th generation / PW3)
  • Firmware: 5.16.2.1.1
  • KPM: v0.2.2 (CLI v1.0.0)
  • Platform reported by KPM: kindlepw2
  • Package: koreader_2026.7.2_kindlepw2.kpkg

Steps and observed result

  1. Ran ;kpm update successfully.
  2. Ran ;kpm search koreader successfully.
  3. Ran ;kpm install koreader over Wi-Fi.
  4. KPM printed Installed 1 package(s) successfully.
  5. No /mnt/us/koreader/ directory or /mnt/us/documents/KOReader.sh scriptlet existed afterward.
  6. The installed_packages table contained only KPM, not KOReader.
  7. KPM's temporary artifact remained at:
    /mnt/us/kmc/kpm/packages/tmp/koreader_2026.7.2_kindlepw2.kpkg

The retained artifact was 32,515,584 bytes. gzip -t failed with unexpected end of file, and tar reported truncated input.

At the time of diagnosis, the server returned Content-Length: 41700710 for the same artifact. A fresh download on the Mac was 41,700,710 bytes, passed gzip -t, contained 1,214 tar entries, and had SHA-256:

d4a933338bbca7c69c9400e2104f5a0af65240d71406e624a65264a841d8a29d

Copying that complete package to /mnt/us/ and invoking KPM locally from a scriptlet succeeded. KPM returned 0, created the KOReader directory and scriptlet, and inserted the KOReader 2026.7.2 row.

Relevant source behavior

On current main at ffa767fffadd731bd59f2bca8c83231f4fc0ab2d, src/install.c calls curl_easy_perform(curl) without retaining or checking the returned CURLcode, then only rejects HTTP response codes >= 400:

KPM/src/install.c

Lines 438 to 467 in ffa767f

curl_easy_setopt(curl, CURLOPT_URL, target_url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, Internal_DownloadWriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &download_data); // This _should_ work
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, Internal_DownloadHeaderCallback);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, &download_data);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); //@FIXME @TODO: Wth?
curl_easy_setopt(curl, CURLOPT_USERAGENT, "kpm/1.0.0");
curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS);
curl_easy_setopt(curl, CURLOPT_FTP_SKIP_PASV_IP, 1L);
curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L);
curl_easy_perform(curl);
long response_code;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
close(fd);
curl_easy_cleanup(curl);
if (response_code >= 400)
{
if (strncmp("file://", target_url, 7) == 0)
{
free(target_url);
kpmIO->log(KPM_VERBOSITY_DEBUG, "artifact is a file:// URL, ignoring error checking"); // @TODO
}
else
{
free(target_url);
kpmIO->log(KPM_VERBOSITY_ERROR, "Curl received an invalid response code: %zu", response_code);
return KPM_CURL_ERROR;
}
}

The code then calculates a SHA-256 string but does not compare it with package metadata (and the current repository manifest does not appear to publish an artifact digest):

KPM/src/install.c

Lines 469 to 484 in ffa767f

// Check the digest
unsigned char md[EVP_MAX_MD_SIZE];
unsigned int md_len;
EVP_DigestFinal_ex(download_data.evp_ctx, md, &md_len);
EVP_MD_CTX_destroy(download_data.evp_ctx);
char* hash_str = malloc(md_len*2 + 1);
hash_str[0] = 0;
for (int md_i = 0; md_i < md_len; md_i++)
{
sprintf(hash_str, "%s%x", hash_str, md[md_i]);
}
kpmIO->log(KPM_VERBOSITY_DEBUG, "Calculated hash for file: %s", hash_str);
free(hash_str);
free(target_url);

Expected behavior

  • Check and propagate the CURLcode from curl_easy_perform.
  • Reject partial transfers such as CURLE_PARTIAL_FILE and remove or clearly quarantine the incomplete temporary artifact.
  • Do not print a successful package count when extraction/hooks did not produce a completed install and database record.
  • Ideally publish and verify artifact SHA-256 values, or at minimum verify the received byte count when Content-Length is available.

I can provide the small text diagnostic logs if useful. No device serial number or account data is involved.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions