{"record":{"id":"71b8e113ff54cc71","repo":"grpc/grpc-java","slug":"files-were-unmodified-before-their-initial-update-71b8e1","errorCode":null,"errorMessage":"Files were unmodified before their initial update. Probably a bug.","messagePattern":"Files were unmodified before their initial update\\. Probably a bug\\.","errorType":"exception","errorClass":"GeneralSecurityException","httpStatus":null,"severity":"error","filePath":"util/src/main/java/io/grpc/util/AdvancedTlsX509TrustManager.java","lineNumber":231,"sourceCode":"    int i = 1;\n    for (X509Certificate cert: trustCerts) {\n      String alias = Integer.toString(i);\n      keyStore.setCertificateEntry(alias, cert);\n      i++;\n    }\n    this.delegateManager = createDelegateTrustManager(keyStore);\n  }\n\n  /**\n   * Updates the trust certificates from a local file path.\n   *\n   * @param trustCertFile  the file on disk holding the trust certificates\n   */\n  public void updateTrustCredentials(File trustCertFile) throws IOException,\n      GeneralSecurityException {\n    long updatedTime = readAndUpdate(trustCertFile, 0);\n    if (updatedTime == 0) {\n      throw new GeneralSecurityException(\n          \"Files were unmodified before their initial update. Probably a bug.\");\n    }\n  }\n\n  /**\n   * Schedules a {@code ScheduledExecutorService} to read trust certificates from a local file path\n   * periodically, and updates the cached trust certs if there is an update. You must close the\n   * returned Closeable before calling this method again or other update methods\n   * ({@link AdvancedTlsX509TrustManager#useSystemDefaultTrustCerts()},\n   * {@link AdvancedTlsX509TrustManager#updateTrustCredentials(X509Certificate[])},\n   * {@link AdvancedTlsX509TrustManager#updateTrustCredentialsFromFile(File)}).\n   * Before scheduling the task, the method synchronously reads and updates trust certificates once.\n   * If the provided period is less than 1 minute, it is automatically adjusted to 1 minute.\n   *\n   * @param trustCertFile  the file on disk holding the trust certificates\n   * @param period the period between successive read-and-update executions\n   * @param unit the time unit of the initialDelay and period parameters\n   * @param executor the executor service we use to read and update the credentials","sourceCodeStart":213,"sourceCodeEnd":249,"githubUrl":"https://github.com/grpc/grpc-java/blob/64daddc1f3d1975670f769f3e97bde8b2ba32d25/util/src/main/java/io/grpc/util/AdvancedTlsX509TrustManager.java#L213-L249","documentation":"This one-shot updateTrustCredentials(File) call returns the file's new modification time from readAndUpdate; a return value of 0 means the file's lastModified() did not change from the initial value (0), so no credentials were read. The library treats this as an internal invariant violation — the very first read should always register a modification time.","triggerScenarios":"Calling public void updateTrustCredentials(File trustCertFile) when readAndUpdate(trustCertFile, 0) returns 0. Since oldTime is 0, this only happens when File.lastModified() returns 0, i.e. the file does not exist or is not readable — normally that path throws the IOException from readAndUpdate first, so reaching 0 here indicates a race or filesystem anomaly (lastModified()==0 but FileInputStream succeeded, e.g. on odd filesystems).","commonSituations":"Files on exotic/network filesystems where lastModified() returns 0; a file deleted or replaced between the lastModified() check and the read; TOCTOU races in rapidly rotating certificate files.","solutions":["Verify the trust certificate file exists and is readable, and that its filesystem reports a valid modification time (File.lastModified() > 0) before calling","Re-read the file / retry the update call once the file is stable on disk","If the file is actively rotated, ensure the rotation is atomic (write temp file + rename) so lastModified() and the read are consistent","Report as a bug if the file is a regular local file with a valid mtime — the library comment says 'Probably a bug'"],"exampleFix":"// before\nmanager.updateTrustCredentials(trustCertFile); // throws if mtime is 0\n// after\nif (trustCertFile.exists() && trustCertFile.lastModified() > 0) {\n  manager.updateTrustCredentials(trustCertFile);\n} else {\n  throw new IOException(\"Trust cert file missing or has invalid mtime: \" + trustCertFile);\n}","handlingStrategy":"validation","validationCode":"if (trustCertFile == null || !trustCertFile.exists() || trustCertFile.lastModified() == 0) {\n  throw new IOException(\"Trust cert file missing or unreportable mtime: \" + trustCertFile);\n}\ntrustManager.updateTrustCredentials(trustCertFile);","typeGuard":null,"tryCatchPattern":"try {\n  manager.updateTrustCredentials(trustCertFile);\n} catch (GeneralSecurityException e) {\n  if (e.getMessage().contains(\"Files were unmodified before their initial update\")) {\n    log.warning(\"Initial trust update failed; verify file mtime/filesystem: \" + trustCertFile);\n  }\n  throw e;\n}","preventionTips":["Check File.lastModified() > 0 before calling updateTrustCredentials","Keep trust files on local filesystems that report modification times","Rotate certificate files atomically via rename","Treat persistent occurrences on ordinary files as a library bug and report it"],"tags":["tls","file-system","trust-credentials","grpc"],"backgroundTag":"internal-invariant-violation","analyzedSha":"64daddc1f3d1975670f769f3e97bde8b2ba32d25","analyzedAt":"2026-09-08T06:14:57.704Z","contentChangedAt":"2026-09-08T06:14:57.704Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}