github/test: Run tests with address sanitizer

We have lots of cgo interaction here so better to check things fully.

This also requires manually checking for leaks, so add support for this.
This commit is contained in:
Marco Trevisan (Treviño) 2023-10-19 03:02:46 +02:00
parent 04ad7bdc73
commit eac1f2d85d
5 changed files with 89 additions and 4 deletions

View File

@ -13,19 +13,38 @@ jobs:
with:
go-version: ${{ matrix.go-version }}
- name: Install PAM
run: sudo apt install -y libpam-dev
run: |
sudo apt update -y
sudo apt install -y libpam-dev
- name: Install Debug symbols
run: |
sudo apt install -y ubuntu-dev-tools
(cd /tmp && pull-lp-ddebs libpam0g $(lsb_release -c -s))
(cd /tmp && pull-lp-ddebs libpam-modules $(lsb_release -c -s))
sudo dpkg -i /tmp/libpam*-dbgsym_*.ddeb
- name: Add a test user
run: sudo useradd -d /tmp/test -p '$1$Qd8H95T5$RYSZQeoFbEB.gS19zS99A0' -s /bin/false test
- name: Checkout code
uses: actions/checkout@v4
- name: Test
run: sudo go test -v -cover -coverprofile=coverage.out ./...
- name: Test with Address Sanitizer
env:
GO_PAM_TEST_WITH_ASAN: true
CGO_CFLAGS: "-O0 -g3 -fno-omit-frame-pointer"
run: |
# Do not run sudo-requiring go tests because as PAM has some leaks in 22.04
go test -v -asan -cover -coverprofile=coverage-asan-tx.out -gcflags=all="-N -l"
# Run the rest of tests normally
sudo go test -v -cover -coverprofile=coverage-asan-module.out -asan -gcflags=all="-N -l" -run Module
sudo go test -C cmd -coverprofile=coverage-asan.out -v -asan -gcflags=all="-N -l" ./...
- name: Generate example module
run: |
rm -f example-module/pam_go.so
go generate -C example-module -v
test -e example-module/pam_go.so
git diff --exit-code example-module
- name: Test
run: sudo go test -v -cover -coverprofile=coverage.out ./...
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3
env:

2
.gitignore vendored
View File

@ -1,4 +1,4 @@
coverage.out
coverage*.out
example-module/*.so
example-module/*.h
cmd/pam-moduler/tests/*/*.so

View File

@ -23,6 +23,7 @@ func ensureNoError(t *testing.T, err error) {
func Test_NewNullModuleTransaction(t *testing.T) {
t.Parallel()
t.Cleanup(maybeDoLeakCheck)
mt := moduleTransaction{}
if mt.handle != nil {
@ -137,6 +138,7 @@ func Test_NewNullModuleTransaction(t *testing.T) {
tc := tc
t.Run(name+"-error-check", func(t *testing.T) {
t.Parallel()
t.Cleanup(maybeDoLeakCheck)
data, err := tc.testFunc(t)
switch d := data.(type) {
@ -202,6 +204,7 @@ func Test_NewNullModuleTransaction(t *testing.T) {
func Test_ModuleTransaction_InvokeHandler(t *testing.T) {
t.Parallel()
t.Cleanup(maybeDoLeakCheck)
mt := &moduleTransaction{}
err := mt.InvokeHandler(nil, 0, nil)
@ -308,6 +311,7 @@ func Test_ModuleTransaction_InvokeHandler(t *testing.T) {
func testMockModuleTransaction(t *testing.T, mt *moduleTransaction) {
t.Helper()
t.Parallel()
t.Cleanup(maybeDoLeakCheck)
tests := map[string]struct {
testFunc func(mock *mockModuleTransaction) (any, error)
@ -898,6 +902,7 @@ func testMockModuleTransaction(t *testing.T, mt *moduleTransaction) {
tc := tc
t.Run(name, func(t *testing.T) {
t.Parallel()
t.Cleanup(maybeDoLeakCheck)
mock := newMockModuleTransaction(&mockModuleTransaction{T: t,
Expectations: tc.mockExpectations, RetData: tc.mockRetData,
ConversationHandler: tc.conversationHandler})

View File

@ -39,6 +39,7 @@ func ensureTransactionEnds(t *testing.T, tx *Transaction) {
}
func TestPAM_001(t *testing.T) {
t.Cleanup(maybeDoLeakCheck)
u, _ := user.Current()
if u.Uid != "0" {
t.Skip("run this test as root")
@ -67,6 +68,7 @@ func TestPAM_001(t *testing.T) {
}
func TestPAM_002(t *testing.T) {
t.Cleanup(maybeDoLeakCheck)
u, _ := user.Current()
if u.Uid != "0" {
t.Skip("run this test as root")
@ -107,6 +109,7 @@ func (c Credentials) RespondPAM(s Style, msg string) (string, error) {
}
func TestPAM_003(t *testing.T) {
t.Cleanup(maybeDoLeakCheck)
u, _ := user.Current()
if u.Uid != "0" {
t.Skip("run this test as root")
@ -128,6 +131,7 @@ func TestPAM_003(t *testing.T) {
}
func TestPAM_004(t *testing.T) {
t.Cleanup(maybeDoLeakCheck)
u, _ := user.Current()
if u.Uid != "0" {
t.Skip("run this test as root")
@ -148,10 +152,14 @@ func TestPAM_004(t *testing.T) {
}
func TestPAM_005(t *testing.T) {
t.Cleanup(maybeDoLeakCheck)
u, _ := user.Current()
if u.Uid != "0" {
t.Skip("run this test as root")
}
if _, found := os.LookupEnv("GO_PAM_TEST_WITH_ASAN"); found {
t.Skip("test fails under ASAN")
}
tx, err := StartFunc("passwd", "test", func(s Style, msg string) (string, error) {
return "secret", nil
})
@ -174,6 +182,7 @@ func TestPAM_005(t *testing.T) {
}
func TestPAM_006(t *testing.T) {
t.Cleanup(maybeDoLeakCheck)
u, _ := user.Current()
if u.Uid != "0" {
t.Skip("run this test as root")
@ -197,6 +206,7 @@ func TestPAM_006(t *testing.T) {
}
func TestPAM_007(t *testing.T) {
t.Cleanup(maybeDoLeakCheck)
u, _ := user.Current()
if u.Uid != "0" {
t.Skip("run this test as root")
@ -223,6 +233,7 @@ func TestPAM_007(t *testing.T) {
}
func TestPAM_ConfDir(t *testing.T) {
t.Cleanup(maybeDoLeakCheck)
u, _ := user.Current()
c := Credentials{
// the custom service always permits even with wrong password.
@ -258,6 +269,7 @@ func TestPAM_ConfDir(t *testing.T) {
}
func TestPAM_ConfDir_FailNoServiceOrUnsupported(t *testing.T) {
t.Cleanup(maybeDoLeakCheck)
if !CheckPamHasStartConfdir() {
t.Skip("this requires PAM with Conf dir support")
}
@ -286,6 +298,7 @@ func TestPAM_ConfDir_FailNoServiceOrUnsupported(t *testing.T) {
}
func TestPAM_ConfDir_InfoMessage(t *testing.T) {
t.Cleanup(maybeDoLeakCheck)
u, _ := user.Current()
var infoText string
tx, err := StartConfDir("echo-service", u.Username,
@ -319,6 +332,7 @@ func TestPAM_ConfDir_InfoMessage(t *testing.T) {
}
func TestPAM_ConfDir_Deny(t *testing.T) {
t.Cleanup(maybeDoLeakCheck)
if !CheckPamHasStartConfdir() {
t.Skip("this requires PAM with Conf dir support")
}
@ -350,6 +364,7 @@ func TestPAM_ConfDir_Deny(t *testing.T) {
}
func TestPAM_ConfDir_PromptForUserName(t *testing.T) {
t.Cleanup(maybeDoLeakCheck)
c := Credentials{
User: "testuser",
// the custom service only cares about correct user name.
@ -375,6 +390,7 @@ func TestPAM_ConfDir_PromptForUserName(t *testing.T) {
}
func TestPAM_ConfDir_WrongUserName(t *testing.T) {
t.Cleanup(maybeDoLeakCheck)
c := Credentials{
User: "wronguser",
Password: "wrongsecret",
@ -403,6 +419,7 @@ func TestPAM_ConfDir_WrongUserName(t *testing.T) {
}
func TestItem(t *testing.T) {
t.Cleanup(maybeDoLeakCheck)
tx, err := StartFunc("passwd", "test", func(s Style, msg string) (string, error) {
return "", nil
})
@ -442,6 +459,7 @@ func TestItem(t *testing.T) {
}
func TestEnv(t *testing.T) {
t.Cleanup(maybeDoLeakCheck)
tx, err := StartFunc("", "", func(s Style, msg string) (string, error) {
return "", nil
})
@ -511,6 +529,7 @@ func TestEnv(t *testing.T) {
func Test_Error(t *testing.T) {
t.Parallel()
t.Cleanup(maybeDoLeakCheck)
if !CheckPamHasStartConfdir() {
t.Skip("this requires PAM with Conf dir support")
}
@ -624,6 +643,7 @@ func Test_Error(t *testing.T) {
}
func Test_Finalizer(t *testing.T) {
t.Cleanup(maybeDoLeakCheck)
if !CheckPamHasStartConfdir() {
t.Skip("this requires PAM with Conf dir support")
}
@ -643,6 +663,7 @@ func Test_Finalizer(t *testing.T) {
}
func TestFailure_001(t *testing.T) {
t.Cleanup(maybeDoLeakCheck)
tx := Transaction{}
_, err := tx.GetEnvList()
if err == nil {
@ -651,6 +672,7 @@ func TestFailure_001(t *testing.T) {
}
func TestFailure_002(t *testing.T) {
t.Cleanup(maybeDoLeakCheck)
tx := Transaction{}
err := tx.PutEnv("")
if err == nil {
@ -659,6 +681,7 @@ func TestFailure_002(t *testing.T) {
}
func TestFailure_003(t *testing.T) {
t.Cleanup(maybeDoLeakCheck)
tx := Transaction{}
err := tx.CloseSession(0)
if err == nil {
@ -667,6 +690,7 @@ func TestFailure_003(t *testing.T) {
}
func TestFailure_004(t *testing.T) {
t.Cleanup(maybeDoLeakCheck)
tx := Transaction{}
err := tx.OpenSession(0)
if err == nil {
@ -675,6 +699,7 @@ func TestFailure_004(t *testing.T) {
}
func TestFailure_005(t *testing.T) {
t.Cleanup(maybeDoLeakCheck)
tx := Transaction{}
err := tx.ChangeAuthTok(0)
if err == nil {
@ -683,6 +708,7 @@ func TestFailure_005(t *testing.T) {
}
func TestFailure_006(t *testing.T) {
t.Cleanup(maybeDoLeakCheck)
tx := Transaction{}
err := tx.AcctMgmt(0)
if err == nil {
@ -691,6 +717,7 @@ func TestFailure_006(t *testing.T) {
}
func TestFailure_007(t *testing.T) {
t.Cleanup(maybeDoLeakCheck)
tx := Transaction{}
err := tx.SetCred(0)
if err == nil {
@ -699,6 +726,7 @@ func TestFailure_007(t *testing.T) {
}
func TestFailure_008(t *testing.T) {
t.Cleanup(maybeDoLeakCheck)
tx := Transaction{}
err := tx.SetItem(User, "test")
if err == nil {
@ -707,6 +735,7 @@ func TestFailure_008(t *testing.T) {
}
func TestFailure_009(t *testing.T) {
t.Cleanup(maybeDoLeakCheck)
tx := Transaction{}
_, err := tx.GetItem(User)
if err == nil {
@ -715,6 +744,7 @@ func TestFailure_009(t *testing.T) {
}
func TestFailure_010(t *testing.T) {
t.Cleanup(maybeDoLeakCheck)
tx := Transaction{}
err := tx.End()
if err != nil {

31
utils.go Normal file
View File

@ -0,0 +1,31 @@
// Package pam provides a wrapper for the PAM application API.
package pam
/*
#ifdef __SANITIZE_ADDRESS__
#include <sanitizer/lsan_interface.h>
#endif
static inline void
maybe_do_leak_check (void)
{
#ifdef __SANITIZE_ADDRESS__
__lsan_do_leak_check();
#endif
}
*/
import "C"
import (
"os"
"runtime"
"time"
)
func maybeDoLeakCheck() {
runtime.GC()
time.Sleep(time.Millisecond * 20)
if os.Getenv("GO_PAM_SKIP_LEAK_CHECK") == "" {
C.maybe_do_leak_check()
}
}