• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

taosdata / TDengine / #4941

27 Jan 2026 10:23AM UTC coverage: 66.868% (+0.04%) from 66.832%
#4941

push

travis-ci

web-flow
fix: asan invalid write issue (#34400)

7 of 8 new or added lines in 2 files covered. (87.5%)

560 existing lines in 126 files now uncovered.

204401 of 305680 relevant lines covered (66.87%)

126915843.15 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

69.61
/source/common/src/tencrypt.c
1
/*
2
 * Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
3
 *
4
 * This program is free software: you can use, redistribute, and/or modify
5
 * it under the terms of the GNU Affero General Public License, version 3
6
 * or later ("AGPL"), as published by the Free Software Foundation.
7
 *
8
 * This program is distributed in the hope that it will be useful, but WITHOUT
9
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10
 * FITNESS FOR A PARTICULAR PURPOSE.
11
 *
12
 * You should have received a copy of the GNU Affero General Public License
13
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14
 */
15

16
#define _DEFAULT_SOURCE
17
#include "tencrypt.h"
18
#include "crypt.h"
19
#include "os.h"
20
#include "tdef.h"
21
#include "tglobal.h"
22

23
/**
24
 * Write file with encryption header using atomic file replacement.
25
 *
26
 * This function writes data to a file with an encryption header at the beginning.
27
 * The encryption header contains:
28
 * - Magic number "tdEncrypt" for quick identification
29
 * - Algorithm identifier (e.g., SM4 = 1)
30
 * - File format version
31
 * - Length of encrypted data
32
 *
33
 * Atomic file replacement strategy:
34
 * 1. Write to temporary file: filepath.tmp.timestamp
35
 * 2. Sync temporary file to disk
36
 * 3. Atomically rename temporary file to target filepath
37
 * 4. Remove old file if rename succeeds
38
 *
39
 * This ensures the operation is atomic - no partial writes or corrupted files
40
 * even if the process is interrupted.
41
 *
42
 * @param filepath Target file path
43
 * @param algorithm Encryption algorithm identifier
44
 * @param data Data buffer to write (can be NULL for empty file with header only)
45
 * @param dataLen Length of data to write (0 for empty file)
46
 * @return 0 on success, error code on failure
47
 */
48
int32_t taosWriteEncryptFileHeader(const char *filepath, int32_t algorithm, const void *data, int32_t dataLen) {
104,304✔
49
  int32_t   code = 0;
104,304✔
50
  int32_t   lino = 0;
104,304✔
51
  TdFilePtr pFile = NULL;
104,304✔
52
  char      tempFile[PATH_MAX] = {0};
104,304✔
53

54
  if (filepath == NULL) {
104,304✔
55
    code = TSDB_CODE_INVALID_PARA;
×
56
    TSDB_CHECK_CODE(code, lino, _exit);
×
57
  }
58

59
  // Validate algorithm
60
  if (algorithm < 0) {
104,304✔
61
    code = TSDB_CODE_INVALID_PARA;
×
62
    TSDB_CHECK_CODE(code, lino, _exit);
×
63
  }
64

65
  // Validate data parameters
66
  if (dataLen > 0 && data == NULL) {
104,304✔
67
    code = TSDB_CODE_INVALID_PARA;
×
68
    TSDB_CHECK_CODE(code, lino, _exit);
×
69
  }
70

71
  // Prepare encryption header (plaintext)
72
  STdEncryptFileHeader header;
104,304✔
73
  memset(&header, 0, sizeof(STdEncryptFileHeader));
104,304✔
74
  strncpy(header.magic, TD_ENCRYPT_FILE_MAGIC, TD_ENCRYPT_MAGIC_LEN - 1);
104,304✔
75
  header.algorithm = algorithm;
104,304✔
76
  header.version = TD_ENCRYPT_FILE_VERSION;
104,304✔
77
  header.dataLen = dataLen;
104,304✔
78

79
  // Create temporary file for atomic write
80
  snprintf(tempFile, sizeof(tempFile), "%s.tmp", filepath);
104,304✔
81

82
  // Open temp file
83
  pFile = taosOpenFile(tempFile, TD_FILE_CREATE | TD_FILE_WRITE | TD_FILE_TRUNC);
104,304✔
84
  if (pFile == NULL) {
104,304✔
85
    code = terrno;
×
86
    TSDB_CHECK_CODE(code, lino, _exit);
×
87
  }
88

89
  // Write header (plaintext)
90
  int64_t written = taosWriteFile(pFile, &header, sizeof(STdEncryptFileHeader));
104,304✔
91
  if (written != sizeof(STdEncryptFileHeader)) {
104,304✔
92
    code = (terrno != 0) ? terrno : TSDB_CODE_FILE_CORRUPTED;
×
93
    TSDB_CHECK_CODE(code, lino, _exit);
×
94
  }
95

96
  // Write data if present
97
  if (dataLen > 0 && data != NULL) {
104,304✔
98
    written = taosWriteFile(pFile, data, dataLen);
104,304✔
99
    if (written != dataLen) {
104,304✔
100
      code = (terrno != 0) ? terrno : TSDB_CODE_FILE_CORRUPTED;
×
101
      TSDB_CHECK_CODE(code, lino, _exit);
×
102
    }
103
  }
104

105
  // Sync to disk
106
  code = taosFsyncFile(pFile);
104,304✔
107
  TSDB_CHECK_CODE(code, lino, _exit);
104,304✔
108

109
  // Close temp file
110
  (void)taosCloseFile(&pFile);
104,304✔
111

112
  // Atomic replacement - rename temp file to target
113
  code = taosRenameFile(tempFile, filepath);
103,569✔
114
  TSDB_CHECK_CODE(code, lino, _exit);
104,304✔
115

116
_exit:
104,304✔
117
  if (pFile != NULL) {
104,304✔
118
    (void)taosCloseFile(&pFile);
×
119
  }
120
  if (code != 0) {
104,304✔
121
    if (tempFile[0] != '\0') {
735✔
122
      (void)taosRemoveFile(tempFile);
735✔
123
    }
124
    uError("%s failed at %s:%d since %s, file:%s", __func__, __FILE__, lino, tstrerror(code), filepath);
735✔
125
    terrno = code;
735✔
126
  }
127
  return code;
104,304✔
128
}
129

130
/**
131
 * Read encryption header from file.
132
 *
133
 * Reads and validates the encryption header from the beginning of a file.
134
 * Checks:
135
 * - Magic number matches "tdEncrypt"
136
 * - Version is supported
137
 *
138
 * @param filepath File path to read
139
 * @param header Output parameter for header data
140
 * @return 0 on success, error code on failure
141
 */
142
int32_t taosReadEncryptFileHeader(const char *filepath, STdEncryptFileHeader *header) {
11,013,723✔
143
  int32_t   code = 0;
11,013,723✔
144
  int32_t   lino = 0;
11,013,723✔
145
  TdFilePtr pFile = NULL;
11,013,723✔
146

147
  if (filepath == NULL || header == NULL) {
11,013,723✔
UNCOV
148
    code = TSDB_CODE_INVALID_PARA;
×
UNCOV
149
    TSDB_CHECK_CODE(code, lino, _exit);
×
150
  }
151

152
  // Open file for reading
153
  pFile = taosOpenFile(filepath, TD_FILE_READ);
11,013,723✔
154
  if (pFile == NULL) {
11,010,946✔
155
    code = terrno;
×
156
    TSDB_CHECK_CODE(code, lino, _exit);
×
157
  }
158

159
  // Read header
160
  int64_t nread = taosReadFile(pFile, header, sizeof(STdEncryptFileHeader));
11,010,946✔
161
  if (nread != sizeof(STdEncryptFileHeader)) {
11,012,052✔
162
    code = TSDB_CODE_FILE_CORRUPTED;
761,918✔
163
    TSDB_CHECK_CODE(code, lino, _exit);
761,918✔
164
  }
165

166
  // Verify magic number
167
  if (strncmp(header->magic, TD_ENCRYPT_FILE_MAGIC, strlen(TD_ENCRYPT_FILE_MAGIC)) != 0) {
10,250,237✔
168
    code = TSDB_CODE_FILE_CORRUPTED;
10,228,268✔
169
    TSDB_CHECK_CODE(code, lino, _exit);
10,228,268✔
170
  }
171

172
  // Verify version (currently only version 1 is supported)
173
  if (header->version != TD_ENCRYPT_FILE_VERSION) {
17,951✔
174
    code = TSDB_CODE_FILE_CORRUPTED;
×
175
    TSDB_CHECK_CODE(code, lino, _exit);
×
176
  }
177

178
_exit:
11,002,087✔
179
  if (pFile != NULL) {
11,007,077✔
180
    (void)taosCloseFile(&pFile);
11,010,091✔
181
  }
182
  if (code != 0) {
11,010,350✔
183
    uDebug("%s failed at %s:%d since %s, file:%s", __func__, __FILE__, lino, tstrerror(code), filepath);
10,994,098✔
184
    terrno = code;
10,994,098✔
185
  }
186
  return code;
11,007,406✔
187
}
188

189
/**
190
 * Check if file has encryption header.
191
 *
192
 * Quickly checks if a file begins with the encryption magic number.
193
 * This is faster than reading the full header when you only need to
194
 * know if the file is encrypted.
195
 *
196
 * @param filepath File path to check
197
 * @param algorithm Output parameter for algorithm (can be NULL)
198
 * @return true if file is encrypted, false otherwise
199
 */
200
bool taosIsEncryptedFile(const char *filepath, int32_t *algorithm) {
13,793,588✔
201
  int32_t lino = 0;
13,793,588✔
202
  
203
  if (filepath == NULL) {
13,793,588✔
204
    terrno = TSDB_CODE_INVALID_PARA;
×
205
    uError("%s failed at %s:%d since %s, file:%s", __func__, __FILE__, lino, tstrerror(terrno), "NULL");
×
206
    return false;
×
207
  }
208

209
  // Check if file exists
210
  if (!taosCheckExistFile(filepath)) {
13,793,588✔
211
    return false;
2,780,070✔
212
  }
213

214
  // Read header
215
  STdEncryptFileHeader header;
11,006,492✔
216
  int32_t              code = taosReadEncryptFileHeader(filepath, &header);
11,013,287✔
217

218
  if (code != 0) {
11,011,999✔
219
    return false;
10,992,733✔
220
  }
221

222
  // Return algorithm if requested
223
  if (algorithm != NULL) {
19,266✔
224
    *algorithm = header.algorithm;
×
225
  }
226

227
  return true;
19,266✔
228
}
229

230
/**
231
 * Write configuration file with encryption support using atomic file replacement.
232
 *
233
 * This function writes a configuration file with optional encryption based on tsCfgKey.
234
 * If tsCfgKey is enabled (not empty), it encrypts the data using SM4 CBC algorithm
235
 * and writes it with an encryption header. Otherwise, it writes the file normally.
236
 *
237
 * Atomic file replacement strategy (same for both encrypted and plain files):
238
 * 1. Write to temporary file: filepath.tmp
239
 * 2. Sync temporary file to disk
240
 * 3. Atomically rename temporary file to target filepath
241
 * 4. Remove old file if rename succeeds
242
 *
243
 * @param filepath Target file path
244
 * @param data Data buffer to write
245
 * @param dataLen Length of data to write
246
 * @return 0 on success, error code on failure
247
 */
248
int32_t taosWriteCfgFile(const char *filepath, const void *data, int32_t dataLen) {
63,977,167✔
249
  int32_t   code = 0;
63,977,167✔
250
  int32_t   lino = 0;
63,977,167✔
251
  TdFilePtr pFile = NULL;
63,977,167✔
252
  char      tempFile[PATH_MAX] = {0};
63,982,080✔
253
  char     *plainBuf = NULL;
63,973,548✔
254
  char     *encryptedBuf = NULL;
63,973,548✔
255

256
  if (filepath == NULL || data == NULL || dataLen <= 0) {
63,973,548✔
257
    code = TSDB_CODE_INVALID_PARA;
462✔
258
    TSDB_CHECK_CODE(code, lino, _exit);
462✔
259
  }
260

261
  snprintf(tempFile, sizeof(tempFile), "%s.tmp", filepath);
63,973,548✔
262

263
  // Check if CFG_KEY encryption is enabled
264
  if (tsCfgKey[0] == '\0') {
63,973,548✔
265
    // No encryption, write file normally with atomic operation
266
    pFile = taosOpenFile(tempFile, TD_FILE_CREATE | TD_FILE_WRITE | TD_FILE_TRUNC);
63,869,408✔
267
    if (pFile == NULL) {
63,866,574✔
268
      code = terrno;
×
269
      TSDB_CHECK_CODE(code, lino, _exit);
×
270
    }
271

272
    if (taosWriteFile(pFile, data, dataLen) != dataLen) {
63,866,574✔
273
      code = (terrno != 0) ? terrno : TSDB_CODE_FILE_CORRUPTED;
×
274
      TSDB_CHECK_CODE(code, lino, _exit);
×
275
    }
276

277
    code = taosFsyncFile(pFile);
63,857,283✔
278
    TSDB_CHECK_CODE(code, lino, _exit);
63,874,917✔
279

280
    (void)taosCloseFile(&pFile);
63,874,917✔
281
    pFile = NULL;
63,866,349✔
282

283
    // Atomic replacement - rename temp file to target
284
    code = taosRenameFile(tempFile, filepath);
63,866,349✔
285
    TSDB_CHECK_CODE(code, lino, _exit);
63,856,268✔
286

287
    return 0;
63,856,268✔
288
  }
289

290
  // Encryption enabled - encrypt data first
291
  int32_t cryptedDataLen = ENCRYPTED_LEN(dataLen);
104,304✔
292

293
  // Allocate buffer for padding
294
  plainBuf = taosMemoryMalloc(cryptedDataLen);
104,304✔
295
  if (plainBuf == NULL) {
104,304✔
296
    code = TSDB_CODE_OUT_OF_MEMORY;
×
297
    TSDB_CHECK_CODE(code, lino, _exit);
×
298
  }
299

300
  // Copy data and zero padding
301
  (void)memset(plainBuf, 0, cryptedDataLen);
104,304✔
302
  (void)memcpy(plainBuf, data, dataLen);
104,304✔
303

304
  // Allocate buffer for encrypted data
305
  encryptedBuf = taosMemoryMalloc(cryptedDataLen);
104,304✔
306
  if (encryptedBuf == NULL) {
104,304✔
307
    code = TSDB_CODE_OUT_OF_MEMORY;
×
308
    TSDB_CHECK_CODE(code, lino, _exit);
×
309
  }
310

311
  // Setup encryption options (similar to walWrite.c)
312
  SCryptOpts opts = {0};
104,304✔
313
  opts.len = cryptedDataLen;
104,304✔
314
  opts.source = plainBuf;
104,304✔
315
  opts.result = encryptedBuf;
104,304✔
316
  opts.unitLen = 16;
104,304✔
317
  opts.pOsslAlgrName = TSDB_ENCRYPT_ALGO_SM4_STR;
104,304✔
318
  tstrncpy((char *)opts.key, tsCfgKey, ENCRYPT_KEY_LEN + 1);
104,304✔
319

320
  // Encrypt the data
321
  int32_t count = Builtin_CBC_Encrypt(&opts);
104,304✔
322
  if (count != opts.len) {
104,304✔
323
    code = TSDB_CODE_FAILED;
×
324
    TSDB_CHECK_CODE(code, lino, _exit);
×
325
  }
326

327
  // Write encrypted file with header (uses atomic operation internally)
328
  code = taosWriteEncryptFileHeader(filepath, TSDB_ENCRYPT_ALGO_SM4, encryptedBuf, cryptedDataLen);
104,304✔
329
  TSDB_CHECK_CODE(code, lino, _exit);
104,304✔
330

331
_exit:
104,304✔
332
  if (pFile != NULL) {
104,304✔
333
    (void)taosCloseFile(&pFile);
×
334
  }
335
  if (plainBuf != NULL) {
104,304✔
336
    taosMemoryFree(plainBuf);
104,304✔
337
  }
338
  if (encryptedBuf != NULL) {
104,304✔
339
    taosMemoryFree(encryptedBuf);
104,304✔
340
  }
341
  if (code != 0) {
104,304✔
342
    if (tempFile[0] != '\0') {
735✔
343
      (void)taosRemoveFile(tempFile);
735✔
344
    }
345
    uError("%s failed at %s:%d since %s, file:%s", __func__, __FILE__, lino, tstrerror(code), filepath);
735✔
346
    terrno = code;
735✔
347
  }
348
  return code;
104,304✔
349
}
350

351
/**
352
 * Read configuration file with automatic decryption support.
353
 *
354
 * This function reads a configuration file and automatically handles decryption if needed.
355
 * It checks if the file has an encryption header:
356
 * - If encrypted: reads header, reads encrypted data, decrypts using tsCfgKey
357
 * - If not encrypted: reads file content directly
358
 *
359
 * The caller is responsible for freeing the returned buffer.
360
 *
361
 * @param filepath File path to read
362
 * @param data Output parameter for data buffer (caller must free)
363
 * @param dataLen Output parameter for data length (actual plaintext length)
364
 * @return 0 on success, error code on failure
365
 */
366
int32_t taosReadCfgFile(const char *filepath, char **data, int32_t *dataLen) {
13,791,060✔
367
  int32_t              code = 0;
13,791,060✔
368
  int32_t              lino = 0;
13,791,060✔
369
  TdFilePtr            pFile = NULL;
13,791,060✔
370
  char                *fileContent = NULL;
13,790,978✔
371
  char                *plainContent = NULL;
13,790,978✔
372
  STdEncryptFileHeader header;
13,780,980✔
373

374
  if (taosWaitCfgKeyLoaded() != 0) {
13,790,854✔
375
    code = terrno;
×
376
    TSDB_CHECK_CODE(code, lino, _exit);
×
377
  }
378

379
  if (filepath == NULL || data == NULL || dataLen == NULL) {
13,790,312✔
380
    code = TSDB_CODE_INVALID_PARA;
×
381
    TSDB_CHECK_CODE(code, lino, _exit);
×
382
  }
383

384
  *data = NULL;
13,790,312✔
385
  *dataLen = 0;
13,791,024✔
386
  // Check if file is encrypted
387
  bool isEncrypted = taosIsEncryptedFile(filepath, NULL);
13,790,209✔
388

389
  // Open file for reading
390
  pFile = taosOpenFile(filepath, TD_FILE_READ);
13,787,581✔
391
  if (pFile == NULL) {
13,789,793✔
392
    code = terrno;
2,780,277✔
393
    TSDB_CHECK_CODE(code, lino, _exit);
2,780,277✔
394
  }
395

396
  // Get file size
397
  int64_t fileSize = 0;
11,009,516✔
398
  code = taosFStatFile(pFile, &fileSize, NULL);
11,007,001✔
399
  TSDB_CHECK_CODE(code, lino, _exit);
11,002,434✔
400

401
  if (fileSize <= 0) {
11,002,434✔
402
    code = TSDB_CODE_FILE_CORRUPTED;
×
403
    TSDB_CHECK_CODE(code, lino, _exit);
×
404
  }
405

406
  if (isEncrypted) {
11,002,434✔
407
    // File is encrypted - read header first
408
    int64_t nread = taosReadFile(pFile, &header, sizeof(STdEncryptFileHeader));
19,266✔
409
    if (nread != sizeof(STdEncryptFileHeader)) {
19,266✔
410
      code = (terrno != 0) ? terrno : TSDB_CODE_FILE_CORRUPTED;
×
411
      TSDB_CHECK_CODE(code, lino, _exit);
×
412
    }
413

414
    // Verify magic number
415
    if (strncmp(header.magic, TD_ENCRYPT_FILE_MAGIC, strlen(TD_ENCRYPT_FILE_MAGIC)) != 0) {
19,266✔
416
      code = TSDB_CODE_FILE_CORRUPTED;
×
417
      TSDB_CHECK_CODE(code, lino, _exit);
×
418
    }
419

420
    // Read encrypted data
421
    int32_t encryptedDataLen = header.dataLen;
19,266✔
422
    if (encryptedDataLen <= 0 || encryptedDataLen > fileSize) {
19,266✔
423
      code = TSDB_CODE_FILE_CORRUPTED;
×
424
      TSDB_CHECK_CODE(code, lino, _exit);
×
425
    }
426

427
    fileContent = taosMemoryMalloc(encryptedDataLen);
19,266✔
428
    if (fileContent == NULL) {
19,266✔
429
      code = TSDB_CODE_OUT_OF_MEMORY;
×
430
      TSDB_CHECK_CODE(code, lino, _exit);
×
431
    }
432

433
    nread = taosReadFile(pFile, fileContent, encryptedDataLen);
19,266✔
434
    if (nread != encryptedDataLen) {
19,266✔
435
      code = (terrno != 0) ? terrno : TSDB_CODE_FILE_CORRUPTED;
×
436
      TSDB_CHECK_CODE(code, lino, _exit);
×
437
    }
438

439
    (void)taosCloseFile(&pFile);
19,266✔
440
    pFile = NULL;
19,266✔
441

442
    // Check if CFG_KEY is available
443
    if (tsCfgKey[0] == '\0') {
19,266✔
444
      code = TSDB_CODE_FAILED;
×
445
      TSDB_CHECK_CODE(code, lino, _exit);
×
446
    }
447

448
    // Decrypt data (reference: sdbFile.c decrypt implementation)
449
    // Allocate buffer for plaintext (same size as encrypted data + 1 for null terminator)
450
    plainContent = taosMemoryMalloc(encryptedDataLen + 1);
19,266✔
451
    if (plainContent == NULL) {
19,266✔
452
      code = TSDB_CODE_OUT_OF_MEMORY;
×
453
      TSDB_CHECK_CODE(code, lino, _exit);
×
454
    }
455

456
    // Setup decryption options
457
    SCryptOpts opts = {0};
19,266✔
458
    opts.len = encryptedDataLen;
19,266✔
459
    opts.source = fileContent;
19,266✔
460
    opts.result = plainContent;
19,266✔
461
    opts.unitLen = 16;
19,266✔
462
    tstrncpy(opts.key, tsCfgKey, ENCRYPT_KEY_LEN + 1);
19,266✔
463

464
    // Decrypt the data
465
    int32_t count = Builtin_CBC_Decrypt(&opts);
19,266✔
466
    if (count != encryptedDataLen) {
19,266✔
467
      code = TSDB_CODE_FAILED;
×
468
      TSDB_CHECK_CODE(code, lino, _exit);
×
469
    }
470

471
    taosMemoryFree(fileContent);
19,266✔
472
    fileContent = NULL;
19,266✔
473

474
    // Add null terminator for JSON parser (cJSON_Parse requires null-terminated string)
475
    plainContent[encryptedDataLen] = '\0';
19,266✔
476

477
    // Return decrypted data
478
    *data = plainContent;
19,266✔
479
    *dataLen = encryptedDataLen;
19,266✔
480
    plainContent = NULL;  // Transfer ownership to caller
19,266✔
481

482
  } else {
483
    // File is not encrypted - read directly
484
    fileContent = taosMemoryMalloc(fileSize + 1);
10,983,168✔
485
    if (fileContent == NULL) {
10,988,255✔
486
      code = TSDB_CODE_OUT_OF_MEMORY;
×
487
      TSDB_CHECK_CODE(code, lino, _exit);
×
488
    }
489

490
    int64_t nread = taosReadFile(pFile, fileContent, fileSize);
10,988,255✔
491
    if (nread != fileSize) {
10,990,084✔
492
      code = (terrno != 0) ? terrno : TSDB_CODE_FILE_CORRUPTED;
×
493
      TSDB_CHECK_CODE(code, lino, _exit);
×
494
    }
495

496
    (void)taosCloseFile(&pFile);
10,990,084✔
497
    pFile = NULL;
10,986,841✔
498

499
    fileContent[fileSize] = '\0';
10,986,841✔
500

501
    // Return file content
502
    *data = fileContent;
10,988,409✔
503
    *dataLen = fileSize;
10,990,358✔
504
    fileContent = NULL;  // Transfer ownership to caller
10,991,068✔
505
  }
506

507
_exit:
13,790,611✔
508
  if (pFile != NULL) {
13,790,982✔
509
    (void)taosCloseFile(&pFile);
×
510
  }
511
  if (fileContent != NULL) {
13,790,982✔
512
    taosMemoryFree(fileContent);
×
513
  }
514
  if (plainContent != NULL) {
13,790,982✔
515
    taosMemoryFree(plainContent);
×
516
  }
517
  if (code != 0) {
13,790,982✔
518
    uError("%s failed at %s:%d since %s, file:%s", __func__, __FILE__, lino, tstrerror(code), filepath);
2,780,277✔
519
    terrno = code;
2,780,277✔
520
  }
521
  return code;
13,790,982✔
522
}
523

524
/**
525
 * Encrypt a single configuration file if it's not already encrypted.
526
 *
527
 * This function checks if a file exists and is not encrypted, then encrypts it in place.
528
 * The operation is atomic - uses temporary file and rename.
529
 *
530
 * @param filepath File path to encrypt
531
 * @return 0 on success or file already encrypted, error code on failure
532
 */
533
static int32_t taosEncryptSingleCfgFile(const char *filepath) {
1,470✔
534
  int32_t   code = 0;
1,470✔
535
  int32_t   lino = 0;
1,470✔
536
  TdFilePtr pFile = NULL;
1,470✔
537
  char     *plainData = NULL;
1,470✔
538

539
  if (filepath == NULL) {
1,470✔
540
    code = TSDB_CODE_INVALID_PARA;
×
541
    TSDB_CHECK_CODE(code, lino, _exit);
×
542
  }
543

544
  // Check if file exists
545
  if (!taosCheckExistFile(filepath)) {
1,470✔
546
    // File doesn't exist, nothing to do
547
    return 0;
×
548
  }
549

550
  // Check if file is already encrypted
551
  if (taosIsEncryptedFile(filepath, NULL)) {
1,470✔
552
    // Already encrypted, nothing to do
553
    return 0;
×
554
  }
555

556
  // Read plaintext file
557
  pFile = taosOpenFile(filepath, TD_FILE_READ);
1,470✔
558
  if (pFile == NULL) {
1,470✔
559
    code = terrno;
×
560
    TSDB_CHECK_CODE(code, lino, _exit);
×
561
  }
562

563
  int64_t fileSize = 0;
1,470✔
564
  code = taosFStatFile(pFile, &fileSize, NULL);
1,470✔
565
  TSDB_CHECK_CODE(code, lino, _exit);
1,470✔
566

567
  if (fileSize <= 0) {
1,470✔
568
    (void)taosCloseFile(&pFile);
×
569
    pFile = NULL;
×
570
    // Empty file, just skip it
571
    return 0;
×
572
  }
573

574
  plainData = taosMemoryMalloc(fileSize);
1,470✔
575
  if (plainData == NULL) {
1,470✔
576
    code = TSDB_CODE_OUT_OF_MEMORY;
×
577
    TSDB_CHECK_CODE(code, lino, _exit);
×
578
  }
579

580
  int64_t nread = taosReadFile(pFile, plainData, fileSize);
1,470✔
581
  if (nread != fileSize) {
1,470✔
582
    code = (terrno != 0) ? terrno : TSDB_CODE_FILE_CORRUPTED;
×
583
    TSDB_CHECK_CODE(code, lino, _exit);
×
584
  }
585

586
  (void)taosCloseFile(&pFile);
1,470✔
587
  pFile = NULL;
1,470✔
588

589
  // Encrypt the file using taosWriteCfgFile (which handles encryption and atomic write)
590
  code = taosWriteCfgFile(filepath, plainData, fileSize);
1,470✔
591
  TSDB_CHECK_CODE(code, lino, _exit);
1,470✔
592

593
_exit:
1,470✔
594
  if (pFile != NULL) {
1,470✔
595
    (void)taosCloseFile(&pFile);
×
596
  }
597
  if (plainData != NULL) {
1,470✔
598
    taosMemoryFree(plainData);
1,470✔
599
  }
600
  if (code != 0) {
1,470✔
601
    uError("%s failed at %s:%d since %s, file:%s", __func__, __FILE__, lino, tstrerror(code), filepath);
735✔
602
  }
603
  return code;
1,470✔
604
}
605

606
/**
607
 * Encrypt existing configuration files that are not yet encrypted.
608
 *
609
 * This function scans common configuration file locations and encrypts any
610
 * plaintext files it finds. It's called after encryption keys are loaded
611
 * to ensure all sensitive config files are encrypted.
612
 *
613
 * Files checked:
614
 * - dnode: dnode.info, dnode.json
615
 * - mnode: mnode.json, raft_config.json, raft_store.json
616
 * - vnode: vnodes.json, vnode.json (all vnodes), raft_config.json, raft_store.json, current.json
617
 * - snode: snode.json
618
 *
619
 * @param dataDir Data directory path (tsDataDir)
620
 * @return 0 on success, error code on failure (first error encountered)
621
 */
622
int32_t taosEncryptExistingCfgFiles(const char *dataDir) {
1,470✔
623
  int32_t code = 0;
1,470✔
624
  int32_t lino = 0;
1,470✔
625
  char    filepath[PATH_MAX];
1,470✔
626

627
  if (dataDir == NULL) {
1,470✔
628
    code = TSDB_CODE_INVALID_PARA;
×
629
    TSDB_CHECK_CODE(code, lino, _exit);
×
630
  }
631

632
  // Check if encryption is enabled
633
  if (tsCfgKey[0] == '\0') {
1,470✔
634
    // Encryption not enabled, nothing to do
635
    return 0;
×
636
  }
637

638
  // 1. Encrypt dnode config files
639
  // dnode.info
640
  // snprintf(filepath, sizeof(filepath), "%s%sdnode%sdnode.info", dataDir, TD_DIRSEP, TD_DIRSEP);
641
  // if (taosCheckExistFile(filepath) && !taosIsEncryptedFile(filepath, NULL)) {
642
  //   code = taosEncryptSingleCfgFile(filepath);
643
  //   TSDB_CHECK_CODE(code, lino, _exit);
644
  //   uInfo("successfully encrypted file %s", filepath);
645
  // }
646

647
  // dnode.json (ep.json)
648
  snprintf(filepath, sizeof(filepath), "%s%sdnode%sdnode.json", dataDir, TD_DIRSEP, TD_DIRSEP);
1,470✔
649
  if (taosCheckExistFile(filepath) && !taosIsEncryptedFile(filepath, NULL)) {
1,470✔
650
    code = taosEncryptSingleCfgFile(filepath);
1,470✔
651
    TSDB_CHECK_CODE(code, lino, _exit);
1,470✔
652
    uInfo("successfully encrypted file %s", filepath);
735✔
653
  }
654

655
  // 2. Encrypt mnode config files
656
  snprintf(filepath, sizeof(filepath), "%s%smnode%smnode.json", dataDir, TD_DIRSEP, TD_DIRSEP);
735✔
657
  if (taosCheckExistFile(filepath) && !taosIsEncryptedFile(filepath, NULL)) {
735✔
UNCOV
658
    code = taosEncryptSingleCfgFile(filepath);
×
UNCOV
659
    TSDB_CHECK_CODE(code, lino, _exit);
×
UNCOV
660
    uInfo("successfully encrypted file %s", filepath);
×
661
  }
662

663
  snprintf(filepath, sizeof(filepath), "%s%smnode%ssync%sraft_config.json", dataDir, TD_DIRSEP, TD_DIRSEP, TD_DIRSEP);
735✔
664
  if (taosCheckExistFile(filepath) && !taosIsEncryptedFile(filepath, NULL)) {
735✔
UNCOV
665
    code = taosEncryptSingleCfgFile(filepath);
×
UNCOV
666
    TSDB_CHECK_CODE(code, lino, _exit);
×
UNCOV
667
    uInfo("successfully encrypted file %s", filepath);
×
668
  }
669

670
  snprintf(filepath, sizeof(filepath), "%s%smnode%ssync%sraft_store.json", dataDir, TD_DIRSEP, TD_DIRSEP, TD_DIRSEP);
735✔
671
  if (taosCheckExistFile(filepath) && !taosIsEncryptedFile(filepath, NULL)) {
735✔
UNCOV
672
    code = taosEncryptSingleCfgFile(filepath);
×
UNCOV
673
    TSDB_CHECK_CODE(code, lino, _exit);
×
UNCOV
674
    uInfo("successfully encrypted file %s", filepath);
×
675
  }
676

677
  // 3. Encrypt snode config files
678
  snprintf(filepath, sizeof(filepath), "%s%ssnode%ssnode.json", dataDir, TD_DIRSEP, TD_DIRSEP);
735✔
679
  if (taosCheckExistFile(filepath) && !taosIsEncryptedFile(filepath, NULL)) {
735✔
680
    code = taosEncryptSingleCfgFile(filepath);
×
681
    TSDB_CHECK_CODE(code, lino, _exit);
×
682
    uInfo("successfully encrypted file %s", filepath);
×
683
  }
684

685
  // 4. Encrypt vnode config files
686
  // vnodes.json
687
  snprintf(filepath, sizeof(filepath), "%s%svnode%svnodes.json", dataDir, TD_DIRSEP, TD_DIRSEP);
735✔
688
  if (taosCheckExistFile(filepath) && !taosIsEncryptedFile(filepath, NULL)) {
735✔
689
    code = taosEncryptSingleCfgFile(filepath);
×
690
    TSDB_CHECK_CODE(code, lino, _exit);
×
691
    uInfo("successfully encrypted file %s", filepath);
×
692
  }
693

694
  // Note: Individual vnode directories (vnode1, vnode2, etc.) are not traversed here
695
  // because they would require scanning the vnode directory structure.
696
  // These files will be encrypted on next write by taosWriteCfgFile.
697

698
  uInfo("finished encrypting existing config files");
735✔
699

700
_exit:
1,470✔
701
  if (code != 0) {
1,470✔
702
    uError("%s failed at %s:%d since %s, dataDir:%s", __func__, __FILE__, lino, tstrerror(code), dataDir);
735✔
703
  }
704
  return code;
1,470✔
705
}
706

707
/**
708
 * Wait for CFG encryption key to be loaded with timeout.
709
 *
710
 * This function polls the encryption key status at regular intervals (100ms).
711
 * It returns immediately if the key is already loaded, otherwise it waits
712
 * until either the key is loaded or the timeout expires.
713
 *
714
 * Timeout is controlled by TD_ENCRYPT_KEY_WAIT_TIMEOUT_MS macro.
715
 *
716
 * @return 0 if key loaded successfully, TSDB_CODE_TIMEOUT_ERROR if timeout occurs
717
 */
718
int32_t taosWaitCfgKeyLoaded(void) {
150,030,064✔
719
#if defined(TD_ENTERPRISE) || defined(TD_ASTRA_TODO)
720
  int32_t code = 0;
150,030,064✔
721
  int32_t lino = 0;
150,030,064✔
722

723
  if (tsSkipKeyCheckMode) {
150,030,064✔
724
    uDebug("skip encryption key verification in some special check mode");
446,413✔
725
    return 0;
446,413✔
726
  }
727

728
  int32_t encryptKeysLoaded = atomic_load_32(&tsEncryptKeysStatus);
149,583,651✔
729
  if (encryptKeysLoaded == TSDB_ENCRYPT_KEY_STAT_LOADED || encryptKeysLoaded == TSDB_ENCRYPT_KEY_STAT_NOT_EXIST ||
149,583,651✔
730
      encryptKeysLoaded == TSDB_ENCRYPT_KEY_STAT_DISABLED) {
731
    uDebug("CFG encryption key loaded successfully");
149,583,651✔
732
    return 0;
149,583,651✔
733
  }
734
  uDebug("CFG encryption key not loaded, waiting for %d ms", TD_ENCRYPT_KEY_WAIT_TIMEOUT_MS);
×
735

736
  const int32_t checkIntervalMs = 100;  // Check every 100ms
×
737
  int32_t       elapsedMs = 0;
×
738

739
  while (elapsedMs < TD_ENCRYPT_KEY_WAIT_TIMEOUT_MS) {
×
740
    // Check if CFG key is loaded
741
    encryptKeysLoaded = atomic_load_32(&tsEncryptKeysStatus);
×
742
    if (encryptKeysLoaded == TSDB_ENCRYPT_KEY_STAT_LOADED || encryptKeysLoaded == TSDB_ENCRYPT_KEY_STAT_NOT_EXIST ||
×
743
        encryptKeysLoaded == TSDB_ENCRYPT_KEY_STAT_DISABLED) {
744
      uDebug("CFG encryption key loaded successfully after %d ms", elapsedMs);
×
745
      return 0;
×
746
    }
747

748
    // Sleep for check interval
749
    taosMsleep(checkIntervalMs);
×
750
    elapsedMs += checkIntervalMs;
×
751
  }
752

753
  // Timeout occurred
754
  code = TSDB_CODE_TIMEOUT_ERROR;
×
755
  lino = __LINE__;
×
756
  uError("%s failed at %s:%d since %s, waited %d ms", __func__, __FILE__, lino, tstrerror(code), elapsedMs);
×
757
  terrno = code;
×
758
  return code;
×
759
#else
760
  uDebug("skip encryption key verification in non-enterprise mode");
761
  return 0;
762
#endif
763
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc