Skip to content

Commit d089c65

Browse files
committed
Handle empty password from broader set of MySQL clients
Some MySQL clients (e.g. libmysql) send a single null byte to indicate an empty password, while others (e.g. mariadb) send an empty packet. This matches MySQL server's own handling: ```c if (!pkt_len || (pkt_len == 1 && *pkt == 0)) ``` (Source: https://github.com/mysql/mysql-server/blob/8.0/sql/auth/sha2_password.cc)
1 parent 8abbc87 commit d089c65

File tree

4 files changed

+61
-6
lines changed

4 files changed

+61
-6
lines changed

server/auth.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ var (
1818
ErrAccessDeniedNoPassword = fmt.Errorf("%w without password", ErrAccessDenied)
1919
)
2020

21+
// isEmptyPassword returns true if the auth data represents an empty password.
22+
// Some clients send an empty packet (len == 0), while others (e.g. MySQL's libmysql)
23+
// send a single null byte. This matches MySQL server's own handling:
24+
// if (!pkt_len || (pkt_len == 1 && *pkt == 0))
25+
// See: https://github.com/mysql/mysql-server/blob/8.0/sql/auth/sha2_password.cc
26+
func isEmptyPassword(authData []byte) bool {
27+
return len(authData) == 0 || (len(authData) == 1 && authData[0] == 0)
28+
}
29+
2130
func (c *Conn) compareAuthData(authPluginName string, clientAuthData []byte) error {
2231
if authPluginName != c.credential.AuthPluginName {
2332
err := c.writeAuthSwitchRequest(c.credential.AuthPluginName)
@@ -66,7 +75,7 @@ func scrambleValidation(cached, nonce, scramble []byte) bool {
6675
}
6776

6877
func (c *Conn) compareNativePasswordAuthData(clientAuthData []byte, credential Credential) error {
69-
if len(clientAuthData) == 0 {
78+
if isEmptyPassword(clientAuthData) {
7079
if credential.Password == "" {
7180
return nil
7281
}
@@ -84,8 +93,7 @@ func (c *Conn) compareNativePasswordAuthData(clientAuthData []byte, credential C
8493
}
8594

8695
func (c *Conn) compareSha256PasswordAuthData(clientAuthData []byte, credential Credential) error {
87-
// Empty passwords are not hashed, but sent as empty string
88-
if len(clientAuthData) == 0 {
96+
if isEmptyPassword(clientAuthData) {
8997
if credential.Password == "" {
9098
return nil
9199
}
@@ -123,8 +131,7 @@ func (c *Conn) compareSha256PasswordAuthData(clientAuthData []byte, credential C
123131
}
124132

125133
func (c *Conn) compareCacheSha2PasswordAuthData(clientAuthData []byte) error {
126-
// Empty passwords are not hashed, but sent as empty string
127-
if len(clientAuthData) == 0 {
134+
if isEmptyPassword(clientAuthData) {
128135
if c.credential.Password == "" {
129136
return nil
130137
}

server/auth_switch_response.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func (c *Conn) handleCachingSha2PasswordFullAuth(authData []byte) error {
7171
}
7272

7373
func (c *Conn) checkSha2CacheCredentials(clientAuthData []byte, credential Credential) error {
74-
if len(clientAuthData) == 0 {
74+
if isEmptyPassword(clientAuthData) {
7575
if credential.Password == "" {
7676
return nil
7777
}

server/auth_switch_response_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,18 @@ func TestCheckSha2CacheCredentials_EmptyPassword(t *testing.T) {
2525
serverPassword: "secret",
2626
wantErr: ErrAccessDeniedNoPassword,
2727
},
28+
{
29+
name: "null byte client auth, empty server password",
30+
clientAuthData: []byte{0x00},
31+
serverPassword: "",
32+
wantErr: nil,
33+
},
34+
{
35+
name: "null byte client auth, non-empty server password",
36+
clientAuthData: []byte{0x00},
37+
serverPassword: "secret",
38+
wantErr: ErrAccessDeniedNoPassword,
39+
},
2840
}
2941

3042
for _, tt := range tests {

server/auth_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,18 @@ func TestCompareNativePasswordAuthData_EmptyPassword(t *testing.T) {
3232
serverPassword: "secret",
3333
wantErr: ErrAccessDeniedNoPassword,
3434
},
35+
{
36+
name: "null byte client auth, empty server password",
37+
clientAuthData: []byte{0x00},
38+
serverPassword: "",
39+
wantErr: nil,
40+
},
41+
{
42+
name: "null byte client auth, non-empty server password",
43+
clientAuthData: []byte{0x00},
44+
serverPassword: "secret",
45+
wantErr: ErrAccessDeniedNoPassword,
46+
},
3547
}
3648

3749
for _, tt := range tests {
@@ -68,6 +80,18 @@ func TestCompareSha256PasswordAuthData_EmptyPassword(t *testing.T) {
6880
serverPassword: "secret",
6981
wantErr: ErrAccessDeniedNoPassword,
7082
},
83+
{
84+
name: "null byte client auth, empty server password",
85+
clientAuthData: []byte{0x00},
86+
serverPassword: "",
87+
wantErr: nil,
88+
},
89+
{
90+
name: "null byte client auth, non-empty server password",
91+
clientAuthData: []byte{0x00},
92+
serverPassword: "secret",
93+
wantErr: ErrAccessDeniedNoPassword,
94+
},
7195
}
7296

7397
for _, tt := range tests {
@@ -104,6 +128,18 @@ func TestCompareCacheSha2PasswordAuthData_EmptyPassword(t *testing.T) {
104128
serverPassword: "secret",
105129
wantErr: ErrAccessDeniedNoPassword,
106130
},
131+
{
132+
name: "null byte client auth, empty server password",
133+
clientAuthData: []byte{0x00},
134+
serverPassword: "",
135+
wantErr: nil,
136+
},
137+
{
138+
name: "null byte client auth, non-empty server password",
139+
clientAuthData: []byte{0x00},
140+
serverPassword: "secret",
141+
wantErr: ErrAccessDeniedNoPassword,
142+
},
107143
}
108144

109145
for _, tt := range tests {

0 commit comments

Comments
 (0)