Skip to content

Keep the Execute action available to users holding only the EXECUTE privilege - #20415

Open
Mano515 wants to merge 1 commit into
phpmyadmin:QA_5_2from
Mano515:fix-19543-execute-privilege
Open

Keep the Execute action available to users holding only the EXECUTE privilege#20415
Mano515 wants to merge 1 commit into
phpmyadmin:QA_5_2from
Mano515:fix-19543-execute-privilege

Conversation

@Mano515

@Mano515 Mano515 commented Aug 9, 2026

Copy link
Copy Markdown

Fixes #19543

Problem

After GRANT EXECUTE ON db.* TO john, john can run CALL Hello('world') from the
MySQL CLI, but the Execute action for that routine is greyed out in phpMyAdmin.

Root cause

The privilege check itself is fine: Util::currentUserHasPrivilege('EXECUTE', $db)
returns true, because INFORMATION_SCHEMA.SCHEMA_PRIVILEGES does report the grant.

The action is disabled by a second, unrelated condition. Routines::getRow() also
needs the routine's parameter list, which it obtains by parsing the routine's DDL:

$definition = $this->dbi->getDefinition($db, $routine['type'], $routine['name']);
$executeAction = '';

if ($definition !== null) {
    // ... only here is $executeAction ever set
}

and the template requires both flags:

{% if has_execute_privilege and execute_action is not empty %}

getDefinition() runs SHOW CREATE PROCEDURE. Reading a routine's source requires
SHOW_ROUTINE, or the global SELECT privilege, or being the routine's DEFINER;
EXECUTE alone is not enough. The server does not reject the statement in that case —
it returns a row whose Create Procedure column is NULL:

| Procedure | sql_mode | Create Procedure | ... |
| Hello     | ONLY_...  | NULL             | ... |

getDefinition() maps that to null, $executeAction stays empty, and the row renders
the disabled bd_nextpage icon. In short, phpMyAdmin was treating "may I read this
routine's source?" as if it answered "may I run it?".

The same $definition === null bail-out exists in getDataFromName(), so simply
enabling the link is not enough: the execution dialog would fail with
"No routine with name ... found in database ...".

Fix

  1. getRow() decides the Execute action from the privilege, and only uses the DDL as an
    optimisation. When the DDL is readable the existing behaviour is unchanged (routines
    with no input parameters still get one-click execute_routine). When it is not, it
    falls back to execute_dialog.
  2. getDataFromName() rebuilds a parameters-only definition from
    INFORMATION_SCHEMA.PARAMETERS when the source is hidden, which the server does
    expose to a user holding EXECUTE. It is fed to the existing parser, so length,
    UNSIGNED, ENUM/SET values and parameter direction are all derived by the same
    code as before.

Because such a rebuilt definition carries no body, it is opt-in through a new
$paramsOnlyData argument that only the two execution paths pass. The routine editor
keeps refusing a routine whose source it cannot read — otherwise saving it back would
recreate the routine with an empty body. There is a regression test for this.

The baseline files only move by three counters, for one extra escapeString($db) call
and one extra (string) cast on a column read from INFORMATION_SCHEMA — both follow the
global $db / mixed-array patterns already used all over this class.

Testing

Verified against MySQL 8.4.2 (the version in the report) and MariaDB 11.4.4, with
exactly the grants from the issue, GRANT USAGE ON *.* + GRANT EXECUTE ON db.*. Both
servers behave the same: SHOW CREATE returns a NULL body, ROUTINE_DEFINITION is
NULL, INFORMATION_SCHEMA.PARAMETERS is readable, and CALL works — so the bug and
the fix are identical on both.

before after
Execute icon ic_bd_nextpage (disabled) ic_b_nextpage (enabled)
Execution dialog "No routine with name ... found" renders the correct fields
CALL Hello('world') not possible SET @p0='world'; CALL Hello(@p0);Hello world
AddUp(10, 0.25) (FUNCTION) not possible SELECT AddUp(@p0, @p1)10.25

Also checked that IN / OUT / INOUT, int unsigned, enum('x','y') and
decimal(10,2) all parse identically whether they come from the real DDL or from the
rebuilt one, and that Edit and Export stay disabled for this user. Both servers report
PARAMETER_MODE for every parameter past position 0, including a function's, so the
rebuilt parameter list never has a missing direction.

Three tests were added. The two that cover the reported bug fail without this change.
phpcs, phpstan and psalm are clean on the changed files, and the test suite shows
no new failures.

Not covered

A grant on one specific routine, GRANT EXECUTE ON PROCEDURE db.Hello TO jane, still
shows a disabled Execute action. Util::currentUserHasPrivilege() only looks at
USER_PRIVILEGES and SCHEMA_PRIVILEGES, and a routine-level grant appears in neither
(it lives in mysql.procs_priv, which the user cannot read) — the long-standing comment
above the check already says as much. master has the same gap.

Closing it would mean changing how the privilege is detected rather than when the
definition is needed, so it felt like a separate change. Happy to fold it in if you would
rather see both handled at once.

Reading a routine's source needs SHOW_ROUTINE, the global SELECT privilege
or being its DEFINER, so SHOW CREATE returns a NULL body to a user who was
only granted EXECUTE. getRow() treated that missing definition as a missing
privilege and greyed out the Execute action, and getDataFromName() bailed
out on it too, which would have broken the execution dialog.

Decide the action from the privilege instead, and rebuild a parameters-only
definition from INFORMATION_SCHEMA.PARAMETERS when the source is hidden.
That rebuilt definition has no body, so it is opt-in and only the execution
paths ask for it: the editor keeps refusing a routine it cannot read, or it
would save the empty body back.

Signed-off-by: Manuel <hmanuel515@hotmail.fr>
@Mano515

Mano515 commented Aug 9, 2026

Copy link
Copy Markdown
Author

The workflows here are waiting on approval, so I ran the same commit on my fork in the meantime:

All green. Mentioning it mainly because the patch bumps one counter in phpstan-baseline.neon and one in psalm-baseline.xml, and I wanted to be sure those held on CI and not just on my machine.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants