Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement process user without cgo #139

Merged
merged 2 commits into from
Oct 20, 2022

Conversation

kruskall
Copy link
Member

While bumping the library in the apm-server I noticed we need the process user for certain features.

Implement process user info retrieval without cgo. This should be the last blocker for the apm-server.

Replace old getProcTaskAllInfo call (requires cgo) with sysctl based call that retrieves data from the kproc_info struct.

Add assertions to make sure the fields are not empty.

Related to #127
Related to elastic/apm-server#8718

@elasticmachine
Copy link
Collaborator

elasticmachine commented Oct 17, 2022

💚 Build Succeeded

the below badges are clickable and redirect to their specific view in the CI or DOCS
Pipeline View Test View Changes Artifacts preview preview

Expand to view the summary

Build stats

  • Start Time: 2022-10-18T07:05:54.411+0000

  • Duration: 9 min 46 sec

Test stats 🧪

Test Results
Failed 0
Passed 200
Skipped 4
Total 204

Copy link
Member

@axw axw left a comment

Choose a reason for hiding this comment

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

Nice to see some more cgo removed, just one detail I'm a bit unsure of.

Comment on lines +128 to +131
egid := ""
if len(kproc.Eproc.Ucred.Groups) > 0 {
egid = strconv.Itoa(int(kproc.Eproc.Ucred.Groups[0]))
}
Copy link
Member

Choose a reason for hiding this comment

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

How did you come up with this logic? I'm not a macOS expert, so I'm just a little bit wary of this since it looks quite different from the old code. I couldn't find any docs on these structs.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good question! The old code was retrieving the GID (primary group id). This code retrieves the groups list (GID+supplementary groups). Since at least one group id is required and I couldn't find any documentation on the ordering I compared the slice with the gid from the old code. The GID was always the first element so I think that was a reasonable assumption to make.

I also compared the other fields to make sure the returned values were the same.

Copy link
Member

Choose a reason for hiding this comment

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

Hmm, but is it the GID, or is it the EGID? That's the main thing I'm concerned about. If I were to guess, I'd have said it was the GID. Can you compare with the output of ps?

Copy link
Member Author

@kruskall kruskall Oct 19, 2022

Choose a reason for hiding this comment

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

Ah sorry, the names are confusing. To clarify:

The rgid is mapped to the GID field of the struct.
The gid is mapped to the EGID field of the struct.

With the new code the behaviour is the same and the first element of the groups array (previously was Pbi_gid) is mapped to the EGID field of the struct.

The behaviour can be verified with this patch:

diff --git a/providers/darwin/process_darwin.go b/providers/darwin/process_darwin.go
index b63367e..baa4f4f 100644
--- a/providers/darwin/process_darwin.go
+++ b/providers/darwin/process_darwin.go
@@ -140,6 +140,22 @@ func (p *process) User() (types.UserInfo, error) {
        }, nil
 }
 
+func (p *process) UserOld() (types.UserInfo, error) {
+       var task procTaskAllInfo
+       if err := getProcTaskAllInfo(p.pid, &task); err != nil {
+               return types.UserInfo{}, err
+       }
+
+       return types.UserInfo{
+               UID:  strconv.Itoa(int(task.Pbsd.Pbi_ruid)),
+               EUID: strconv.Itoa(int(task.Pbsd.Pbi_uid)),
+               SUID: strconv.Itoa(int(task.Pbsd.Pbi_svuid)),
+               GID:  strconv.Itoa(int(task.Pbsd.Pbi_rgid)),
+               EGID: strconv.Itoa(int(task.Pbsd.Pbi_gid)),
+               SGID: strconv.Itoa(int(task.Pbsd.Pbi_svgid)),
+       }, nil
+}
+
 func (p *process) Environment() (map[string]string, error) {
        return p.env, nil
 }
diff --git a/providers/darwin/process_darwin_test.go b/providers/darwin/process_darwin_test.go
index 1d2dfc5..08bfcfc 100644
--- a/providers/darwin/process_darwin_test.go
+++ b/providers/darwin/process_darwin_test.go
@@ -127,3 +127,39 @@ func TestProcesses(t *testing.T) {
 
        t.Log(ps[0].Info())
 }
+
+func TestCompareUser(t *testing.T) {
+       var s darwinSystem
+       ps, err := s.Processes()
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       for _, p := range ps {
+               pp := p.(*process)
+               u, err := pp.User()
+               if err != nil {
+                       t.Logf("err for pid %d with new user code: %v\n", pp.pid, err)
+                       continue
+               }
+
+               oldU, err := pp.UserOld()
+               if err != nil {
+                       t.Logf("err for pid %d with old user code: %v\n", pp.pid, err)
+                       continue
+               }
+
+               if oldU.EGID != oldU.GID  {
+                       t.Logf("EGID!=GID for pid: %d\n", p.PID())
+                       t.Logf("OLD: GID=%s SGID=%s EGID=%s\n", oldU.GID, oldU.SGID, oldU.EGID)
+                       t.Logf("NEW: GID=%s SGID=%s EGID=%s\n", oldU.GID, oldU.SGID, oldU.EGID)
+               }
+
+               require.Equal(t, oldU.UID, u.UID, "UID")
+               require.Equal(t, oldU.EUID, u.EUID, "EUID")
+               require.Equal(t, oldU.SUID, u.SUID, "SUID")
+               require.Equal(t, oldU.GID, u.GID, "GID")
+               require.Equal(t, oldU.SGID, u.SGID, "SGID")
+               require.Equal(t, oldU.EGID, u.EGID, "EGID")
+       }
+}

The test passes because the behaviour is the same and it also outputs the specific example where GID and EGID differs but the information retrieved by the old and new code is the same:

    process_darwin_test.go:153: EGID!=GID for pid: 149
    process_darwin_test.go:154: OLD: GID=0 SGID=0 EGID=88
    process_darwin_test.go:155: NEW: GID=0 SGID=0 EGID=88

Copy link
Member

Choose a reason for hiding this comment

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

Thanks! That is convincing enough for me.

@kruskall kruskall merged commit 53d6396 into elastic:main Oct 20, 2022
@kruskall kruskall deleted the feat/darwin-proc-user-nocgo branch October 20, 2022 07:30
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.

3 participants