Skip to content

fix(rbac): access control[manual-only] #35213

Open
kailixu wants to merge 21 commits intomainfrom
fix/TD-6837020675-main
Open

fix(rbac): access control[manual-only] #35213
kailixu wants to merge 21 commits intomainfrom
fix/TD-6837020675-main

Conversation

@kailixu
Copy link
Copy Markdown
Contributor

@kailixu kailixu commented Apr 22, 2026

Description

Issue(s)

  • Close/close/Fix/fix/Resolve/resolve: Issue Link

Checklist

Please check the items in the checklist if applicable.

  • Is the user manual updated?
  • Are the test cases passed and automated?
  • Is there no significant decrease in test coverage?

Copilot AI review requested due to automatic review settings April 22, 2026 11:05
@kailixu kailixu requested review from a team, dapan1121 and guanshengliang as code owners April 22, 2026 11:05
@kailixu kailixu changed the title fix(rbac): access control fix(rbac): access control[manual-only] Apr 22, 2026
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the database privilege check logic to distinguish between rollup and trim operations and enhances the test suite with RSMA creation and rollup privilege tests. However, a large block of existing test cases was inadvertently commented out, which should be restored, and a leftover comment string needs to be removed for code cleanliness.

Comment thread test/cases/25-Privileges/test_priv_control.py Outdated
Comment thread test/cases/25-Privileges/test_priv_control.py
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adjusts RBAC enforcement for ROLLUP DATABASE by mapping the privilege check to a dedicated rollup operation (instead of treating it like TRIM DATABASE), and updates the privilege test suite to set up RSMA prerequisites for rollup scenarios.

Changes:

  • Update mndProcessTrimDbReq() to check MND_OPER_ROLLUP_DB when trimReq.optrType == TSDB_OPTR_ROLLUP.
  • Extend the rollup privilege test to create a stable + RSMA before running ROLLUP DATABASE.
  • Comment out large portions of the privilege control test suite (table/row/column/RBAC/system/etc. sections).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
test/cases/25-Privileges/test_priv_control.py Adds RSMA creation for rollup testing, but also disables many test sections by commenting them out.
source/dnode/mnode/impl/src/mndDb.c Routes DB privilege checks to ROLLUP vs TRIM based on STrimDbReq.optrType.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread test/cases/25-Privileges/test_priv_control.py Outdated
# Test: user can rollup database with privilege
self.login(user, pwd)
'''BUG20
'''BUG20 '''
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'''BUG20 ''' is a standalone triple-quoted string statement. If this is meant to be a comment/marker, prefer a normal # comment (or a tracked TODO with issue ID) to avoid leaving an unused runtime string literal in the test body.

Suggested change
'''BUG20 '''
# BUG20

Copilot uses AI. Check for mistakes.
Comment thread test/cases/25-Privileges/test_priv_control.py
Comment thread source/dnode/mnode/impl/src/mndDb.c
Copilot AI review requested due to automatic review settings April 22, 2026 12:53
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread test/cases/25-Privileges/test_priv_control.py Outdated
Comment thread test/cases/25-Privileges/test_priv_control.py
Comment thread test/cases/25-Privileges/test_priv_control.py
Copilot AI review requested due to automatic review settings April 23, 2026 10:06
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copilot AI review requested due to automatic review settings April 24, 2026 05:10
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (1)

source/libs/executor/src/sysscanoperator.c:5248

  • doDestroySysTableScanInfo unconditionally calls tsem_destroy(&pInfo->ready), but createSysTableScanOperatorInfo only calls tsem_init(&pInfo->ready, ...) in the non-local-scan path. For the local-scan cases (TABLES, TAGS, FILESETS), ready is never initialized, so destroying it can fail (and is undefined depending on platform semaphore implementation).

Consider initializing ready for all paths, or guard tsem_destroy behind a flag / pInfo->self > 0 / pInfo->readHandle.mnd != NULL as appropriate.

  SSysTableScanInfo* pInfo = (SSysTableScanInfo*)param;
  int32_t            code = tsem_destroy(&pInfo->ready);
  if (code != TSDB_CODE_SUCCESS) {
    qError("%s failed at line %d since %s", __func__, __LINE__, tstrerror(code));
  }
  blockDataDestroy(pInfo->pRes);

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +5343 to 5359
// Release our acquired ref BEFORE posting the semaphore.
// If we post first, the waiter can race ahead: task completes → taosRemoveRef
// drops the count to 1, then doDestroyTask frees the task memory pool (which
// owns pInfo). Our subsequent taosReleaseRef would then drop the count to 0
// and call doDestroySysTableScanInfo on already-freed memory.
// By releasing first (count 2→1, destructor not triggered), pInfo remains
// valid for the tsem_post call below, and doDestroySysTableScanInfo is
// called only later, inside destroySysScanOperator, when pInfo is still live.
int32_t refCode = taosReleaseRef(sysTableScanRefPool, pWrapper->sysTableScanId);
if (refCode != TSDB_CODE_SUCCESS) {
qError("%s failed at line %d since %s", __func__, __LINE__, tstrerror(refCode));
}

int32_t res = tsem_post(&pInfo->ready);
if (res != TSDB_CODE_SUCCESS) {
qError("%s failed at line %d since %s", __func__, __LINE__, tstrerror(res));
}
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In loadSysTableCallback, the code releases the acquired ref (taosReleaseRef) before calling tsem_post(&pInfo->ready). If the operator has already removed its own ref (via taosRemoveRef) while this callback is in-flight, the taosReleaseRef here can drop the refcount to 0 and trigger doDestroySysTableScanInfo, freeing pInfo before tsem_post executes → use-after-free on pInfo->ready.

To make this safe, keep the acquired ref held until after tsem_post (or otherwise guarantee the semaphore outlives pInfo, e.g., post via a separately-owned semaphore / task-owned wait primitive).

Suggested change
// Release our acquired ref BEFORE posting the semaphore.
// If we post first, the waiter can race ahead: task completes → taosRemoveRef
// drops the count to 1, then doDestroyTask frees the task memory pool (which
// owns pInfo). Our subsequent taosReleaseRef would then drop the count to 0
// and call doDestroySysTableScanInfo on already-freed memory.
// By releasing first (count 2→1, destructor not triggered), pInfo remains
// valid for the tsem_post call below, and doDestroySysTableScanInfo is
// called only later, inside destroySysScanOperator, when pInfo is still live.
int32_t refCode = taosReleaseRef(sysTableScanRefPool, pWrapper->sysTableScanId);
if (refCode != TSDB_CODE_SUCCESS) {
qError("%s failed at line %d since %s", __func__, __LINE__, tstrerror(refCode));
}
int32_t res = tsem_post(&pInfo->ready);
if (res != TSDB_CODE_SUCCESS) {
qError("%s failed at line %d since %s", __func__, __LINE__, tstrerror(res));
}
// Keep the acquired ref until after posting the semaphore. The semaphore is
// embedded in pInfo, so releasing the ref first can drop the refcount to 0
// and destroy pInfo before tsem_post() dereferences pInfo->ready.
int32_t res = tsem_post(&pInfo->ready);
if (res != TSDB_CODE_SUCCESS) {
qError("%s failed at line %d since %s", __func__, __LINE__, tstrerror(res));
}
int32_t refCode = taosReleaseRef(sysTableScanRefPool, pWrapper->sysTableScanId);
if (refCode != TSDB_CODE_SUCCESS) {
qError("%s failed at line %d since %s", __func__, __LINE__, tstrerror(refCode));
}

Copilot uses AI. Check for mistakes.
Comment on lines +4980 to 4987
// Block this worker thread until the response arrives. qSemWait notifies
// the worker pool and waits, then re-acquires on wake-up.
code = qSemWait((qTaskInfo_t)pTaskInfo, &pInfo->ready);
if (code != TSDB_CODE_SUCCESS) {
freeSysTableLoadCtx(pLoadCtx);
qError("%s failed at line %d since %s", __func__, __LINE__, tstrerror(code));
qError("%s tsem_wait failed at line %d since %s", __func__, __LINE__, tstrerror(code));
pTaskInfo->code = code;
T_LONG_JMP(pTaskInfo->env, code);
}
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sysTableScanFromMNode previously used tsem_timewait(..., VTB_REF_RPC_TIMEOUT_MS) but now blocks with qSemWait(..., &pInfo->ready) which has no timeout (it wraps tsem_wait). If the RPC callback is never invoked (transport error, dropped response, cancellation edge case), this worker thread can block indefinitely.

If a timeout is still required, consider adding a timed wait variant (e.g., qSemTimeWait) or restoring tsem_timewait and handling TSDB_CODE_TIMEOUT_ERROR explicitly.

Copilot uses AI. Check for mistakes.
Comment on lines 4232 to 4308
# Database privilege tests
print("[Database Privileges]")
self.create_snode()
self.create_qnode()
self.do_create_database_privilege()
self.do_alter_database_privilege()
self.do_drop_database_privilege()
self.do_use_database_privilege()
self.do_show_databases_privilege()
self.do_show_create_database_privilege()
self.do_flush_database_privilege()
self.do_compact_database_privilege()
self.do_trim_database_privilege()
self.do_rollup_database_privilege()
self.do_scan_database_privilege()
self.do_ssmigrate_database_privilege()

# Table privilege tests
print("")
print("[Table Privileges]")
self.do_create_table_privilege()
self.do_drop_table_privilege()
self.do_alter_table_privilege()
self.do_select_privilege()
self.do_insert_privilege()
self.do_delete_privilege()
self.do_select_column_privilege_comprehensive()
self.do_insert_column_privilege_comprehensive()
self.do_show_create_table_privilege()

# Column and row privilege tests
print("")
print("[Column and Row Privileges]")
self.do_row_privilege_with_tag_condition()
self.do_row_privilege_complex_conditions()
self.do_row_privilege_time_range()
self.do_row_privilege_mixed_conditions()
self.do_column_privilege()
self.do_column_mask_privilege()
self.do_column_row_combined_privilege()
self.do_column_privilege_update_priority()
self.do_privilege_update_time_priority()

# RBAC tests
print("")
print("[Role-Based Access Control]")
self.do_role_privilege()
self.do_role_creation_and_grant()
#self.do_role_lock_unlock() #can cause core BUG21
self.do_system_roles()
self.do_audit_database_privileges()

# System privilege tests
print("")
print("[System Privileges]")
self.do_user_management_privileges()
self.do_token_management_privileges()
self.do_totp_management_privileges()
self.do_password_management_privileges()
self.do_node_management_privileges()
self.do_mount_management_privileges()
# self.do_create_database_privilege()
# self.do_alter_database_privilege()
# self.do_drop_database_privilege()
# self.do_use_database_privilege()
# self.do_show_databases_privilege()
# self.do_show_create_database_privilege()
# self.do_flush_database_privilege()
# self.do_compact_database_privilege()
# self.do_trim_database_privilege()
# self.do_rollup_database_privilege()
# self.do_scan_database_privilege()
# self.do_ssmigrate_database_privilege()

# # # Table privilege tests
# print("")
# print("[Table Privileges]")
# self.do_create_table_privilege()
# self.do_drop_table_privilege()
# self.do_alter_table_privilege()
# self.do_select_privilege()
# self.do_insert_privilege()
# self.do_delete_privilege()
# self.do_select_column_privilege_comprehensive()
# self.do_insert_column_privilege_comprehensive()
# self.do_show_create_table_privilege()

# # # Column and row privilege tests
# print("")
# print("[Column and Row Privileges]")
# self.do_row_privilege_with_tag_condition()
# self.do_row_privilege_complex_conditions()
# self.do_row_privilege_time_range()
# self.do_row_privilege_mixed_conditions()
# self.do_column_privilege()
# self.do_column_mask_privilege()
# self.do_column_row_combined_privilege()
# self.do_column_privilege_update_priority()
# self.do_privilege_update_time_priority()

# # # RBAC tests
# print("")
# print("[Role-Based Access Control]")
# self.do_role_privilege()
# self.do_role_creation_and_grant()
# #self.do_role_lock_unlock() #can cause core BUG21
# self.do_system_roles()
# self.do_audit_database_privileges()

# # # System privilege tests
# print("")
# print("[System Privileges]")
# self.do_user_management_privileges()
# self.do_token_management_privileges()
# self.do_totp_management_privileges()
# self.do_password_management_privileges()
# self.do_node_management_privileges()
# self.do_mount_management_privileges()
self.do_system_variable_privileges()
self.do_information_schema_privileges()
self.do_system_monitoring_privileges()
self.do_show_grants_cluster_apps_privileges()
self.do_privilege_delegation()

# Function/index/tsrma/rsma privilege tests
print("")
print("[Function and Index Privileges]")
self.do_create_function_privilege()
self.do_create_index_privilege()
if platform.system().lower() != 'windows':
# windows does not support tsma
self.do_create_tsma_privilege()
self.do_create_rsma_privilege()
# self.do_information_schema_privileges()
# self.do_system_monitoring_privileges()
# self.do_show_grants_cluster_apps_privileges()
# self.do_privilege_delegation()

# # # Function/index/tsrma/rsma privilege tests
# print("")
# print("[Function and Index Privileges]")
# self.do_create_function_privilege()
# self.do_create_index_privilege()
# if platform.system().lower() != 'windows':
# # windows does not support tsma
# self.do_create_tsma_privilege()
# self.do_create_rsma_privilege()

Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_priv_control() now comments out the majority of the privilege test suite (database/table/RBAC/system/etc.) and effectively only runs do_system_variable_privileges(). This significantly reduces automated coverage for RBAC/privilege behavior and makes regressions much harder to catch.

If the intent is "manual-only", consider gating the full suite behind an explicit flag (env var / CLI option) or marking individual tests as skipped, rather than commenting out the calls in the main test entrypoint.

Copilot uses AI. Check for mistakes.
Comment on lines 2721 to 2724
self.exec_sql_failed("SHOW QUERIES", TSDB_CODE_PAR_PERMISSION_DENIED)
'''BUG17
'''BUG17'''
self.exec_sql_failed("SHOW CONNECTIONS", TSDB_CODE_PAR_PERMISSION_DENIED)
'''

Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do_system_monitoring_privileges still has inconsistent handling for the SHOW CONNECTIONS negative check: the earlier "without privilege" assertion is commented out via a triple-quoted block, but after revoke the assertion is executed (the '''BUG17''' line is now just a no-op string). This makes the test coverage uneven and the intent unclear.

Consider removing the triple-quote blocks entirely and using a normal comment/skip mechanism so SHOW CONNECTIONS is either tested in both places or explicitly skipped in both.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings April 25, 2026 07:38
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.

Comments suppressed due to low confidence (1)

source/libs/executor/src/sysscanoperator.c:5247

  • doDestroySysTableScanInfo() always calls tsem_destroy(&pInfo->ready), but pInfo->ready is only initialized via tsem_init() in the remote/MNode path. For local-scan operators (e.g. TABLES/TAGS/FILESETS), this may destroy an uninitialized semaphore and can crash or return undefined errors. Track initialization (e.g. a readyInited flag) and only destroy when initialized, or initialize ready for all code paths.
  SSysTableScanInfo* pInfo = (SSysTableScanInfo*)param;
  int32_t            code = tsem_destroy(&pInfo->ready);
  if (code != TSDB_CODE_SUCCESS) {
    qError("%s failed at line %d since %s", __func__, __LINE__, tstrerror(code));
  }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +4235 to +4259
# self.do_create_database_privilege()
# self.do_alter_database_privilege()
# self.do_drop_database_privilege()
# self.do_use_database_privilege()
# self.do_show_databases_privilege()
# self.do_show_create_database_privilege()
# self.do_flush_database_privilege()
# self.do_compact_database_privilege()
# self.do_trim_database_privilege()
# self.do_rollup_database_privilege()
# self.do_scan_database_privilege()
# self.do_ssmigrate_database_privilege()

# # # Table privilege tests
# print("")
# print("[Table Privileges]")
# self.do_create_table_privilege()
# self.do_drop_table_privilege()
# self.do_alter_table_privilege()
# self.do_select_privilege()
# self.do_insert_privilege()
# self.do_delete_privilege()
# self.do_select_column_privilege_comprehensive()
# self.do_insert_column_privilege_comprehensive()
# self.do_show_create_table_privilege()
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_priv_control() now comments out almost the entire privilege test suite (DB/Table/RBAC/System/etc.) and effectively only runs do_information_schema_privileges(). This will drastically reduce regression detection in CI; please avoid disabling tests by commenting out calls—use the framework’s skip/mark mechanism or gate the “manual-only” subset behind an explicit runtime flag/env var while keeping the default run comprehensive.

Suggested change
# self.do_create_database_privilege()
# self.do_alter_database_privilege()
# self.do_drop_database_privilege()
# self.do_use_database_privilege()
# self.do_show_databases_privilege()
# self.do_show_create_database_privilege()
# self.do_flush_database_privilege()
# self.do_compact_database_privilege()
# self.do_trim_database_privilege()
# self.do_rollup_database_privilege()
# self.do_scan_database_privilege()
# self.do_ssmigrate_database_privilege()
# # # Table privilege tests
# print("")
# print("[Table Privileges]")
# self.do_create_table_privilege()
# self.do_drop_table_privilege()
# self.do_alter_table_privilege()
# self.do_select_privilege()
# self.do_insert_privilege()
# self.do_delete_privilege()
# self.do_select_column_privilege_comprehensive()
# self.do_insert_column_privilege_comprehensive()
# self.do_show_create_table_privilege()
self.do_create_database_privilege()
self.do_alter_database_privilege()
self.do_drop_database_privilege()
self.do_use_database_privilege()
self.do_show_databases_privilege()
self.do_show_create_database_privilege()
self.do_flush_database_privilege()
self.do_compact_database_privilege()
self.do_trim_database_privilege()
self.do_rollup_database_privilege()
self.do_scan_database_privilege()
self.do_ssmigrate_database_privilege()
# # # Table privilege tests
print("")
print("[Table Privileges]")
self.do_create_table_privilege()
self.do_drop_table_privilege()
self.do_alter_table_privilege()
self.do_select_privilege()
self.do_insert_privilege()
self.do_delete_privilege()
self.do_select_column_privilege_comprehensive()
self.do_insert_column_privilege_comprehensive()
self.do_show_create_table_privilege()

Copilot uses AI. Check for mistakes.
Comment on lines +4980 to +4984
// Block this worker thread until the response arrives. qSemWait notifies
// the worker pool and waits, then re-acquires on wake-up.
code = qSemWait((qTaskInfo_t)pTaskInfo, &pInfo->ready);
if (code != TSDB_CODE_SUCCESS) {
freeSysTableLoadCtx(pLoadCtx);
qError("%s failed at line %d since %s", __func__, __LINE__, tstrerror(code));
qError("%s tsem_wait failed at line %d since %s", __func__, __LINE__, tstrerror(code));
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sysTableScanFromMNode() switched from a timed wait (tsem_timewait(..., VTB_REF_RPC_TIMEOUT_MS)) to an unbounded qSemWait(...). If the RPC response is dropped or the callback never fires, this worker thread can block indefinitely. Consider restoring a bounded wait (or making qSemWait time-bounded) and handling timeout/cancellation explicitly to avoid query hangs.

Copilot uses AI. Check for mistakes.
Comment thread source/libs/parser/src/parTranslater.c Outdated
Comment on lines +7207 to +7209
// Fast path: if all sys-table priv bits are set, any table is accessible.
// privInfo bits 3-8: privInfoBasic|privInfoPrivileged|privInfoAudit|privInfoSec|privPerfBasic|privPerfPrivileged
if ((pParCxt->privInfo & 0x01F8u) == 0x01F8u) return 0;
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

transCheckSysTablePriv() uses a hard-coded bitmask (0x01F8u) for sys-table privilege bits. This is brittle and easy to break if the bit layout changes; please replace it with named constants/macros (or compute from the actual bitfield definitions) so the intent and maintenance contract are explicit.

Copilot uses AI. Check for mistakes.
Comment on lines +7217 to +7218
bool isInfo = (dbName[0] == 'i' || dbName[0] == 'I'); // information_schema or INFORMATION_SCHEMA
bool allowed = false;
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

System DB detection here relies on dbName[0] being 'i'/'I' to decide information_schema vs performance_schema. This is error-prone (e.g., if dbName is qualified or ever changes format) and duplicates logic elsewhere in the codebase (there are existing helpers like IS_INFORMATION_SCHEMA_DB(...)). Prefer using the existing schema-db detection helpers/macros instead of first-character checks.

Copilot uses AI. Check for mistakes.
Comment on lines +985 to +989
if (dbName[0] == 'i' || dbName[0] == 'I') {
getInfosDbMeta(&pMeta, &size);
} else {
getPerfDbMeta(&pMeta, &size);
}
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getSysTableMeta() decides which meta set to scan by checking only dbName[0] ('i'/'I' vs else). This is brittle and can misclassify if dbName is ever qualified (e.g. acct.db) or if additional system DBs are introduced. Prefer explicit comparisons/macros for information_schema / performance_schema (or reuse existing helpers) rather than first-character heuristics.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings April 25, 2026 10:48
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +55 to +61
uint64_t mndBuildSysPrivBatchMask(SMnode *pMnode, SUserObj *pUser, const char *token,
const EPrivType *privTypes, int32_t numPrivTypes) {
if (numPrivTypes <= 0) {
return 0;
}
return numPrivTypes >= 64 ? UINT64_MAX : ((1ULL << numPrivTypes) - 1);
}
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mndBuildSysPrivBatchMask() is implemented inside #ifndef _PRIVILEGE and the file has no #else branch, so when TD_PRIVILEGE adds -D_PRIVILEGE (defines _PRIVILEGE) this function will not be compiled, but it is now referenced from mndConfig.c under TD_ENTERPRISE → potential link failure in enterprise+privilege builds. Also, the current implementation ignores pMnode/pUser/token/privTypes and just returns a full bitmask, which defeats the intended per-privilege filtering.

Consider providing a real implementation for _PRIVILEGE builds (e.g. iterate privTypes and check each via the privilege subsystem) and/or moving the declaration/definition out of the stub-only #ifndef _PRIVILEGE block so it is always available.

Copilot uses AI. Check for mistakes.
Comment on lines +4258 to +4268
# self.do_create_database_privilege()
# self.do_alter_database_privilege()
# self.do_drop_database_privilege()
# self.do_use_database_privilege()
# self.do_show_databases_privilege()
# self.do_show_create_database_privilege()
# self.do_flush_database_privilege()
# self.do_compact_database_privilege()
# self.do_trim_database_privilege()
# self.do_rollup_database_privilege()
# self.do_scan_database_privilege()
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_priv_control() now comments out the majority of privilege test cases (database/table/RBAC/system/etc.), leaving only do_system_variable_privileges() enabled. This significantly reduces automated coverage for RBAC/access-control behavior and risks letting regressions slip through.

If these cases are meant to be manual-only, consider gating them behind an explicit runtime flag/marker (so CI still runs them when desired) rather than commenting them out, or split manual cases into a separate test file/suite.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings April 26, 2026 09:07
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

source/libs/parser/src/parTranslater.c:14659

  • Password privilege selection is currently wrong: targetUser is computed but never used, and the code compares authRsp.user with pParCxt->pUser (which should always match). That means altering another user's password will still be treated as "change own password" and checked against PRIV_PASS_ALTER_SELF, bypassing/ignoring PRIV_PASS_ALTER. Compare the current user against the target username instead (and drop the unused targetUser variable).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@@ -690,7 +690,7 @@ static int32_t mndProcessDropMountReq(SRpcMsg *pReq) {
}

// mount operation share the privileges of db
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as the create path: the code checks mndCheckOperPrivilege(..., MND_OPER_DROP_MOUNT) but the surrounding comment still states the mount operation shares DB privileges. Please align the comment and the actual privilege enforcement to avoid confusion and future regressions.

Suggested change
// mount operation share the privileges of db
// drop mount operation requires the dedicated drop-mount privilege

Copilot uses AI. Check for mistakes.
Comment on lines +434 to +455
static uint8_t getShowVarPrivMask(SRequestObj* pRequest) {
SCatalog* pCatalog = NULL;
SGetUserAuthRsp authRsp = {0};
STscObj* pTscObj = pRequest->pTscObj;
SRequestConnInfo conn = {.pTrans = pTscObj->pAppInfo->pTransporter,
.requestId = pRequest->requestId,
.requestObjRefId = pRequest->self,
.mgmtEps = getEpSet_s(&pTscObj->pAppInfo->mgmtEp)};

if (TSDB_CODE_SUCCESS != catalogGetHandle(pTscObj->pAppInfo->clusterId, &pCatalog)) {
return 0;
}
if (TSDB_CODE_SUCCESS != catalogGetUserAuth(pCatalog, &conn, pTscObj->user, &authRsp)) {
return 0;
}

uint8_t mask = 0;
if (PRIV_HAS(&authRsp.sysPrivs, PRIV_VAR_SYSTEM_SHOW)) mask |= SHOW_VAR_PRIV_SYSTEM;
if (PRIV_HAS(&authRsp.sysPrivs, PRIV_VAR_SECURITY_SHOW)) mask |= SHOW_VAR_PRIV_SECURITY;
if (PRIV_HAS(&authRsp.sysPrivs, PRIV_VAR_AUDIT_SHOW)) mask |= SHOW_VAR_PRIV_AUDIT;
if (PRIV_HAS(&authRsp.sysPrivs, PRIV_VAR_DEBUG_SHOW)) mask |= SHOW_VAR_PRIV_DEBUG;
return mask;
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getShowVarPrivMask() calls catalogGetUserAuth(...), which populates heap-owned members inside SGetUserAuthRsp (hash tables, etc.). The function returns without calling tFreeSGetUserAuthRsp(&authRsp), leaking memory on every SHOW LOCAL VARIABLES execution. Ensure authRsp is freed on all paths (success and failure) after extracting the needed privilege bits.

Copilot uses AI. Check for mistakes.
@@ -519,7 +519,7 @@ static int32_t mndProcessCreateMountReq(SRpcMsg *pReq) {
}
}
// mount operation share the privileges of db
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says "mount operation share the privileges of db", but the code now checks mndCheckOperPrivilege(..., MND_OPER_CREATE_MOUNT) instead of a DB-scoped privilege check. Either update the comment to reflect the new privilege model, or (if it truly should be DB-scoped) restore a DB privilege check with the correct DB object/context.

Suggested change
// mount operation share the privileges of db
// mount creation uses mount-specific operation and grant checks

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings April 26, 2026 12:03
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

source/libs/parser/src/parTranslater.c:14666

  • In translateCheckUserOptsPriv(), the password privilege branch compares authRsp.user with pParCxt->pUser, which will always match because catalogGetUserAuth() was requested for pParCxt->pUser. This makes the code always enforce PRIV_PASS_ALTER_SELF even when changing another user’s password, and also leaves targetUser unused. Compare targetUser against the current user (or pParCxt->pUser) to decide between PRIV_PASS_ALTER vs PRIV_PASS_ALTER_SELF.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +4249 to +4256
# self.do_create_database_privilege()
# self.do_alter_database_privilege()
# self.do_drop_database_privilege()
# self.do_use_database_privilege()
# self.do_show_databases_privilege()
# self.do_show_create_database_privilege()
# self.do_flush_database_privilege()
# self.do_compact_database_privilege()
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_priv_control() now comments out almost the entire privilege test suite (DB/table/RBAC/system/function/view/stream/etc.) while the docstring and labels still describe it as a comprehensive CI test. This effectively removes coverage for most privilege checks and could let RBAC regressions slip through CI. If the intent is to make this "manual-only", consider gating sections behind an explicit flag/environment variable or splitting a dedicated, smaller manual test case instead of permanently commenting out the calls.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings April 26, 2026 12:38
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 26 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

source/libs/parser/src/parTranslater.c:14660

  • The password-change privilege check compares authRsp.user with pParCxt->pUser, which are both the current user, so it will always take the “self password” path even when altering another user’s password. targetUser is computed but never used. Compare the target username being altered against the current user to decide between PRIV_PASS_ALTER vs PRIV_PASS_ALTER_SELF.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread test/cases/24-Users/test_user_passwd.py Outdated
tdLog.info(f"api path: {apiPath}")
if platform.system().lower() == 'linux':
p = subprocess.Popen(f"cd {apiPath} && make", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p = subprocess.Popen(f"cd {apiPath} && make -f make_partial", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Linux this invokes make -f make_partial, but the added file in script/api/ is named makefile_partial. As-is the build step will fail with “No rule to make target 'make_partial'”. Update the filename (or add the expected makefile) so the test can compile passwdTest.c reliably.

Suggested change
p = subprocess.Popen(f"cd {apiPath} && make -f make_partial", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p = subprocess.Popen(f"cd {apiPath} && make -f makefile_partial", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings April 26, 2026 16:01
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 27 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread test/cases/24-Users/test_user_passwd.py Outdated
Comment on lines +41 to +46
p = subprocess.Popen(f"cd {apiPath} && make -f make_partial", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = p.communicate()
if 0 != p.returncode:
tdLog.exit("Test script passwdTest.c make failed")
else:
p = subprocess.Popen(f"cd {apiPath} && jom -f makefile_win64.mak", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p = subprocess.Popen(f"cd {apiPath} && jom -f makefile_partial_win64.mak", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Linux build path: make -f make_partial refers to a non-existent makefile in script/api (the added file is makefile_partial). This will fail the passwd test on Linux unless there is an untracked make_partial file in the environment. Update the command to point at the actual filename (or rename the makefile to match).

Copilot uses AI. Check for mistakes.
Comment on lines 5323 to 5331
if (TSDB_CODE_SUCCESS == code) {
pCtx->pRsp = pMsg->pData;
pMsg->pData = NULL;
pCtx->rspCode = TSDB_CODE_SUCCESS;
pInfo->pRsp = pMsg->pData;

SRetrieveMetaTableRsp* pRsp = pCtx->pRsp;
SRetrieveMetaTableRsp* pRsp = pInfo->pRsp;
pRsp->numOfRows = htonl(pRsp->numOfRows);
pRsp->useconds = htobe64(pRsp->useconds);
pRsp->handle = htobe64(pRsp->handle);
pRsp->compLen = htonl(pRsp->compLen);
} else {
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loadSysTableCallback() stores pMsg->pData into pInfo->pRsp on success, but it does not clear pMsg->pData. In this codebase, callbacks that take ownership typically set pMsg->pData = NULL to prevent the RPC framework / caller cleanup from freeing it after the callback returns. As-is, pInfo->pRsp can become a dangling pointer (use-after-free) or be double-freed when sysTableScanFromMNode() later frees pInfo->pRsp.

Copilot uses AI. Check for mistakes.
Comment on lines +443 to +448
if (TSDB_CODE_SUCCESS != catalogGetHandle(pTscObj->pAppInfo->clusterId, &pCatalog)) {
return 0;
}
if (TSDB_CODE_SUCCESS != catalogGetUserAuth(pCatalog, &conn, pTscObj->user, &authRsp)) {
return 0;
}
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getShowVarPrivMask() returns 0 on catalog/auth lookup failures, which will silently filter out all rows from SHOW LOCAL VARIABLES (enterprise) and make operational issues look like "no variables" instead of surfacing an error. Consider propagating the underlying error back to the caller (so the command fails) or at least logging and falling back to a safe default behavior consistent with the product requirements.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings April 26, 2026 16:46
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 28 out of 29 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

source/libs/parser/src/parTranslater.c:14666

  • In translateCheckUserOptsPriv(), the password privilege check compares authRsp.user with pParCxt->pUser, which are effectively the same current user, so the code will always take the “own password” branch and require PRIV_PASS_ALTER_SELF even when altering another user. targetUser is computed but never used. Please compare the current user (pParCxt->pUser) to the target user being altered/created (targetUser) to decide between PRIV_PASS_ALTER vs PRIV_PASS_ALTER_SELF.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants