Repository: kolide/launcher

Issue:
`kolide_app_icons` does not work for most apps on Tahoe
Querying for most apps results in `Error: error generating table: decoding tiff bytes: tiff: unsupported feature: sample format`.

Discussion:

Solution patch:
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index ea333aa8..097f6ca5 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -60,7 +60,13 @@ jobs:
     - name: Get dependencies
       run: make deps
 
+    - name: Build (debug)
+      if: github.ref_type != 'tag'
+      shell: bash
+      run: MAKE_debugsymbols=true make -j2 github-build
+    
     - name: Build
+      if: github.ref_type == 'tag'
       run: make -j2 github-build
 
     - name: Check macOS build target
@@ -138,7 +144,12 @@ jobs:
     - name: Set up zig
       uses: mlugg/setup-zig@v2
 
+    - name: Build (debug)
+      if: github.ref_type != 'tag'
+      run: MAKE_debugsymbols=true make -j2 github-build
+
     - name: Build
+      if: github.ref_type == 'tag'
       run: make -j2 github-build
 
     - name: Cache build output
diff --git a/ee/tables/app-icons/app_icons_darwin.go b/ee/tables/app-icons/app_icons_darwin.go
index 464f3cc5..80c91de5 100644
--- a/ee/tables/app-icons/app_icons_darwin.go
+++ b/ee/tables/app-icons/app_icons_darwin.go
@@ -11,10 +11,33 @@ void Icon(CFDataRef *iconDataRef, char* path) {
 	NSString *appPath = [[NSString stringWithUTF8String:path] stringByStandardizingPath];
 	NSImage *img = [[NSWorkspace sharedWorkspace] iconForFile:appPath];
 
-	//request 128x128 since we are going to resize the icon
+	// request 128x128 since we are going to resize the icon
 	NSRect targetFrame = NSMakeRect(0, 0, 128, 128);
-	CGImageRef cgref = [img CGImageForProposedRect:&targetFrame context:nil hints:nil];
-	NSBitmapImageRep *brep = [[NSBitmapImageRep alloc] initWithCGImage:cgref];
+
+	// Create an 8-bit-per-sample RGBA bitmap and draw the image into it. This
+	// ensures TIFFRepresentation uses a supported sample format for decoding
+	// in Go's tiff decoder.
+	NSBitmapImageRep *brep = [[NSBitmapImageRep alloc]
+		initWithBitmapDataPlanes:NULL
+		pixelsWide:128
+		pixelsHigh:128
+		bitsPerSample:8
+		samplesPerPixel:4
+		hasAlpha:YES
+		isPlanar:NO
+		colorSpaceName:NSDeviceRGBColorSpace
+		bytesPerRow:0
+		bitsPerPixel:0];
+
+    NSGraphicsContext *gctx = [NSGraphicsContext graphicsContextWithBitmapImageRep:brep];
+    @try {
+        [NSGraphicsContext saveGraphicsState];
+        [NSGraphicsContext setCurrentContext:gctx];
+        [img drawInRect:targetFrame fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:1.0];
+    } @finally {
+        [NSGraphicsContext restoreGraphicsState];
+    }
+
 	NSData *imageData = [brep TIFFRepresentation];
 	*iconDataRef = (CFDataRef)imageData;
 }
@@ -64,7 +87,7 @@ func generateAppIcons(ctx context.Context, queryContext table.QueryContext) ([]m
 		return nil, errors.New("The kolide_app_icons table requires that you specify a constraint WHERE path =")
 	}
 	path := q.Constraints[0].Expression
-	img, hash, err := getAppIcon(path, queryContext)
+	img, hash, err := getAppIcon(path)
 	if err != nil {
 		return nil, err
 	}
@@ -84,7 +107,7 @@ func generateAppIcons(ctx context.Context, queryContext table.QueryContext) ([]m
 	return results, nil
 }
 
-func getAppIcon(appPath string, queryContext table.QueryContext) (image.Image, uint64, error) {
+func getAppIcon(appPath string) (image.Image, uint64, error) {
 	var data C.CFDataRef = 0
 	C.Icon(&data, C.CString(appPath))
 	defer C.CFRelease(C.CFTypeRef(data))
diff --git a/pkg/make/builder.go b/pkg/make/builder.go
index 58471b19..fafb8672 100644
--- a/pkg/make/builder.go
+++ b/pkg/make/builder.go
@@ -298,8 +298,10 @@ func (b *Builder) BuildCmd(src, appName string) func(context.Context) error {
 				"os", b.os,
 				"arch", b.arch,
 			)
-			// also include the following gcflags to -N (disable optimizations) and -l (disable inlining) for improved debugging
-			baseArgs = append(baseArgs, `-gcflags=all=-N -l`)
+			// In the future, we may want to also append `-gcflags=all=-N -l`.
+			// -N disables optimizations, and -l disables inlining, so they may give us improved debugging.
+			// For now, though, we don't include them because they cause the following error on Windows ARM:
+			// "syscall.Syscall15: nosplit stack over 792 byte limit".
 		}
 
 		if b.os == "windows" {



Test patch:
diff --git a/ee/tables/app-icons/app_icons_darwin_test.go b/ee/tables/app-icons/app_icons_darwin_test.go
new file mode 100644
index 00000000..86d20411
--- /dev/null
+++ b/ee/tables/app-icons/app_icons_darwin_test.go
@@ -0,0 +1,85 @@
+//go:build darwin
+
+package appicons
+
+import (
+	"bytes"
+	"context"
+	"encoding/base64"
+	"image/png"
+	"testing"
+
+	"github.com/osquery/osquery-go/plugin/table"
+	"github.com/stretchr/testify/require"
+)
+
+func Test_generateAppIcons(t *testing.T) {
+	t.Parallel()
+
+	tests := []struct {
+		name         string
+		queryContext table.QueryContext
+	}{
+		{
+			name: "happy path",
+			queryContext: table.QueryContext{
+				Constraints: map[string]table.ConstraintList{
+					"path": {Affinity: "TEXT", Constraints: []table.Constraint{{Operator: table.OperatorEquals, Expression: "/Applications/safari.app"}}},
+				},
+			},
+		},
+		{
+			name: "not a real file", // this just returns a generic file icon on macOS "looks like a sheet of paper with a little fold in the corner"
+			queryContext: table.QueryContext{
+				Constraints: map[string]table.ConstraintList{
+					"path": {Affinity: "TEXT", Constraints: []table.Constraint{{Operator: table.OperatorEquals, Expression: "/not_a_real_file.app"}}},
+				},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+
+			results, err := generateAppIcons(context.Background(), tt.queryContext)
+			require.NoError(t, err)
+			require.Len(t, results, 1)
+			iconB64 := results[0]["icon"]
+			require.NotEmpty(t, iconB64)
+
+			iconBytes, err := base64.StdEncoding.DecodeString(iconB64)
+			require.NoError(t, err)
+
+			img, err := png.Decode(bytes.NewReader(iconBytes))
+			require.NoError(t, err)
+			require.Equal(t, 128, img.Bounds().Dx())
+			require.Equal(t, 128, img.Bounds().Dy())
+
+			require.NotEmpty(t, results[0]["hash"])
+
+			// code blow is a good for sanity check if you want to ensure the files are being decoded properly
+			// will save images to ./test_images and you can look and see how pretty they are
+
+			/*
+
+				// Save the decoded PNG under ./test_images using the app name from the query constraint
+				err = os.MkdirAll("./test_images", 0o755)
+				require.NoError(t, err)
+
+				// derive a file name from the provided path constraint if present
+				appPath := ""
+				if cl, ok := tt.queryContext.Constraints["path"]; ok && len(cl.Constraints) > 0 {
+					appPath = cl.Constraints[0].Expression
+				}
+				if appPath == "" {
+					appPath = "icon"
+				}
+				name := strings.TrimSuffix(filepath.Base(appPath), ".app")
+				outPath := filepath.Join("./test_images", name+".png")
+
+				err = os.WriteFile(outPath, iconBytes, 0o644)
+				require.NoError(t, err)
+			*/
+		})
+	}
+}