pastebin - collaborative debugging tool
nrubsig.kpaste.net RSS


winsg.c - run Win32 or Cygwin program with a different group
Posted by Anonymous on Sat 25th May 2024 11:00
raw | new post
modification of post by Anonymous (view diff)

  1.  
  2. /*
  3.  * MIT License
  4.  *
  5.  * Copyright (c) 2024 Roland Mainz <roland.mainz@nrubsig.org>
  6.  *
  7.  * Permission is hereby granted, free of charge, to any person obtaining a copy
  8.  * of this software and associated documentation files (the "Software"), to deal
  9.  * in the Software without restriction, including without limitation the rights
  10.  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11.  * copies of the Software, and to permit persons to whom the Software is
  12.  * furnished to do so, subject to the following conditions:
  13.  *
  14.  * The above copyright notice and this permission notice shall be included in all
  15.  * copies or substantial portions of the Software.
  16.  *
  17.  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18.  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19.  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20.  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21.  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22.  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  23.  * SOFTWARE.
  24.  */
  25.  
  26. /*
  27.  * winsg.c - run Win32 or Cygwin program with a different (primary) group
  28.  *
  29.  * Written by Roland Mainz <roland.mainz@nrubsig.org>
  30.  */
  31.  
  32. /*
  33.  * Compile with:
  34.  * $ clang -target x86_64-pc-windows-gnu -Wall -DUNICODE=1 -D_UNICODE=1 -g winsg.c -o winsg.exe #
  35.  */
  36.  
  37. #include <windows.h>
  38. #include <stdio.h>
  39. #include <stdbool.h>
  40. #include <assert.h>
  41. #include <Lmcons.h>
  42. #include <process.h>
  43.  
  44. #if 0
  45. #define D(x) x
  46. #else
  47. #define D(x)
  48. #endif
  49.  
  50. #ifdef _WIN64
  51. #define CYGWIN_BASH_PATH "C:\\cygwin64\\bin\\bash.exe"
  52. #else
  53. #define CYGWIN_BASH_PATH "C:\\cygwin\\bin\\bash.exe"
  54. #endif /* _WIN64 */
  55. #define WIN32_CMDEXE_PATH "C:\\Windows\\system32\\cmd.exe"
  56.  
  57. /*
  58.  * DECLARE_SID_BUFFER - declare a buffer for a SID value
  59.  * Note that buffers with SID values must be 16byte aligned
  60.  * on Windows 32, othewise the kernel might return
  61.  * |ERROR_NOACCESS|(=998) - "Invalid access to memory location.
  62.  */
  63. #define DECLARE_SID_BUFFER(varname) \
  64.     char (varname)[SECURITY_MAX_SID_SIZE+1] __attribute__((aligned(16)))
  65.  
  66. /*
  67.  * Performance hack:
  68.  * GETTOKINFO_EXTRA_BUFFER - extra space for more data
  69.  * |GetTokenInformation()| for |TOKEN_USER| and |TOKEN_PRIMARY_GROUP|
  70.  * always fails in Win10 with |ERROR_INSUFFICIENT_BUFFER| if you
  71.  * just pass the |sizeof(TOKEN_*)| value. Instead of calling
  72.  * |GetTokenInformation()| with |NULL| arg to obtain the size to
  73.  * allocate we just provide 2048 bytes of extra space after the
  74.  * |TOKEN_*| size, and pray it is enough
  75.  */
  76. #define GETTOKINFO_EXTRA_BUFFER (2048)
  77.  
  78. D(
  79. static
  80. bool get_token_primarygroup_name(HANDLE tok, char *out_buffer)
  81. {
  82.     DWORD tokdatalen;
  83.     PTOKEN_PRIMARY_GROUP ptpgroup;
  84.     PSID pgsid;
  85.     DWORD namesize = GNLEN+1;
  86.     char domainbuffer[UNLEN+1];
  87.     DWORD domainbuffer_size = sizeof(domainbuffer);
  88.     SID_NAME_USE name_use;
  89.  
  90.     tokdatalen = sizeof(TOKEN_PRIMARY_GROUP)+GETTOKINFO_EXTRA_BUFFER;
  91.     ptpgroup = _alloca(tokdatalen);
  92.     if (!GetTokenInformation(tok, TokenPrimaryGroup, ptpgroup,
  93.         tokdatalen, &tokdatalen)) {
  94.         D((void)fprintf(stderr, "get_token_primarygroup_name: "
  95.             "GetTokenInformation(tok=0x%p, TokenPrimaryGroup) failed, "
  96.             "status=%d.\n",
  97.             (void *)tok, (int)GetLastError()));
  98.         return false;
  99.     }
  100.  
  101.     pgsid = ptpgroup->PrimaryGroup;
  102.  
  103.     if (!LookupAccountSidA(NULL, pgsid, out_buffer, &namesize,
  104.         domainbuffer, &domainbuffer_size, &name_use)) {
  105.         D((void)fprintf(stderr, "get_token_primarygroup_name: "
  106.             "LookupAccountSidA() failed, status=%d.\n",
  107.             (int)GetLastError()));
  108.         return false;
  109.     }
  110.  
  111.     return true;
  112. }
  113. )
  114.  
  115. static
  116. bool is_group_in_token(HANDLE tok, PSID qsid)
  117. {
  118.     DWORD tokdatalen;
  119.     PTOKEN_GROUPS ptgroups;
  120.  
  121.     tokdatalen = sizeof(TOKEN_GROUPS)+GETTOKINFO_EXTRA_BUFFER;
  122.     ptgroups = _alloca(tokdatalen);
  123.     if (!GetTokenInformation(tok, TokenGroups, ptgroups,
  124.         tokdatalen, &tokdatalen)) {
  125.         D((void)fprintf(stderr, "is_group_in_token: "
  126.             "GetTokenInformation(tok=0x%p, TokenGroups) failed, "
  127.             "status=%d.\n",
  128.             (void *)tok, (int)GetLastError()));
  129.         return false;
  130.     }
  131.  
  132.     int i;
  133.     D(
  134.         (void)fprintf(stderr, "is_group_in_token: got %d groups\n",
  135.             (int)ptgroups->GroupCount)
  136.     );
  137.     for (i = 0 ; i < ptgroups->GroupCount ; i++) {
  138.         if (EqualSid(qsid, ptgroups->Groups[i].Sid) &&
  139.             (ptgroups->Groups[i].Attributes & SE_GROUP_ENABLED)) {
  140.             D((void)puts("is_group_in_token: #match"));
  141.             return true;
  142.         }
  143.     }
  144.  
  145.     D((void)puts("is_group_in_token: #no match"));
  146.  
  147.     return false;
  148. }
  149.  
  150. static
  151. bool get_group_sid(const char *groupname, PSID pgsid, PDWORD pgsid_size)
  152. {
  153.     char domainbuffer[UNLEN+1];
  154.     DWORD domainbuffer_size = sizeof(domainbuffer);
  155.     SID_NAME_USE name_use;
  156.  
  157.     if (!LookupAccountNameA(NULL, groupname,
  158.         pgsid, pgsid_size, domainbuffer, &domainbuffer_size, &name_use)) {
  159.         D((void)fprintf(stderr, "get_group_sid: "
  160.             "LookupAccountNameA() failed.\n"));
  161.         return false;
  162.     }
  163.  
  164.     return true;
  165. }
  166.  
  167. static
  168. bool set_token_primarygroup_sid(HANDLE tok, PSID pgsid)
  169. {
  170.     DWORD tokdatalen;
  171.     TOKEN_PRIMARY_GROUP tpgroup;
  172.  
  173.     tokdatalen = sizeof(TOKEN_PRIMARY_GROUP);
  174.     tpgroup.PrimaryGroup = pgsid;
  175.     if (!SetTokenInformation(tok, TokenPrimaryGroup,
  176.         &tpgroup, tokdatalen)) {
  177.         D((void)fprintf(stderr, "set_token_primarygroup_sid: "
  178.             "SetTokenInformation(tok=0x%p, TokenPrimaryGroup) failed, "
  179.             "status=%d\n",
  180.             (void *)tok, (int)GetLastError()));
  181.         return false;
  182.     }
  183.  
  184.     return true;
  185. }
  186.  
  187. static
  188. char *stpcpy (char *restrict s1, const char *restrict s2)
  189. {
  190.     size_t l = strlen(s2);
  191.     return memcpy(s1, s2, l+1) + l;
  192. }
  193.  
  194. static
  195. void win32cmd_quotearg(char *s1, const char *s2)
  196. {
  197.     int c;
  198.     *s1++ = '"';
  199.     while ((c = *s2) != '\0') {
  200.         switch(c) {
  201.             case '"':
  202.                 *s1++='\\';
  203.                 *s1 = c;
  204.                 break;
  205.             default:
  206.                 *s1 = c;
  207.                 break;
  208.         }
  209.         s1++;
  210.         s2++;
  211.     }
  212.     *s1++ = '"';
  213.     *s1 = '\0';
  214. }
  215.  
  216. static
  217. int usage(void)
  218. {
  219.     (void)fprintf(stderr, "Usage: "
  220.         "winsg [-] -g group [-c command | /C command]]\n"
  221.         "Execute command as different primary group ID\n"
  222.         "\n"
  223.         "Examples:\n"
  224.         "\t1. Run new cmd.exe with primary group 'abc1':\n"
  225.         "\t\twinsg -g abc1 /C\n"
  226.         "\n"
  227.         "\t2. Run new Cygwin bash with primary group 'abc2':\n"
  228.         "\t\twinsg -g abc2 /C\n"
  229.         "\n"
  230.         "\t3. Start /bin/id from cmd.exe with primary group 'abc3':\n"
  231.         "\t\twinsg abc3 /C 'C:\\cygwin64\\bin\\id.exe -a'\n"
  232.         "\n"
  233.         "\t4. Start /bin/id from Cygwin bash with primary group "
  234.             "'abc4':\n"
  235.         "\t\twinsg abc4 -c '/bin/id.exe -a'\n"
  236.         "\n"
  237.         "Please report bugs to "
  238.         "Roland Mainz <roland.mainz@nrubsig.org>.\n");
  239.  
  240.     return 2;
  241. }
  242.  
  243.  
  244. enum shelltype {
  245.     SHELLTYPE_NOT_SET = 0,
  246.     SHELLTYPE_NONE,
  247.     SHELLTYPE_CMD,
  248.     SHELLTYPE_SYSTEM
  249. };
  250.  
  251. int main(int ac, char *av[])
  252. {
  253.     enum shelltype st = SHELLTYPE_NOT_SET;
  254.     int cmd_arg_index = -1;
  255.     const char *newgrpname = NULL;
  256.     HANDLE tok = INVALID_HANDLE_VALUE;
  257.     int subcmdret = EXIT_FAILURE;
  258.     int retval = 1;
  259.     int i;
  260.  
  261.     for (i=1 ; i < ac ; i++) {
  262.         D((void)fprintf(stderr, "# i=%d, av[i]='%s'\n", i, av[i]));
  263.  
  264.         if (!strcmp(av[i], "-")) {
  265.             (void)fprintf(stderr, "%s: "
  266.                 "Run in new login not supported yet.\n", av[0]);
  267.             retval = 1;
  268.             goto done;
  269.         }
  270.         else if (!strcmp(av[i], "-c")) {
  271.             /* -c can take zero or one argument */
  272.             if ((ac-i) > 2) {
  273.                 (void)fprintf(stderr, "%s: "
  274.                     "Too many arguments for -c.\n", av[0]);
  275.                 retval = 1;
  276.                 goto done;
  277.             }
  278.  
  279.             st = SHELLTYPE_SYSTEM;
  280.             cmd_arg_index = i+1;
  281.             break;
  282.         }
  283.         else if (!strcmp(av[i], "/C")) {
  284.             /* /C can take zero or one argument */
  285.             if ((ac-i) > 2) {
  286.                 (void)fprintf(stderr, "%s: "
  287.                     "Too many arguments for /C.\n", av[0]);
  288.                 retval = 1;
  289.                 goto done;
  290.             }
  291.  
  292.             st = SHELLTYPE_CMD;
  293.             cmd_arg_index = i+1;
  294.             break;
  295.         }
  296.         else if (!strcmp(av[i], "-g")) {
  297.             newgrpname = av[i+1];
  298.             i++;
  299.         }
  300.         else if ((!strcmp(av[i], "-h")) ||
  301.                 (!strcmp(av[i], "--help")) ||
  302.                 (!strcmp(av[i], "--usage"))) {
  303.             retval = usage();
  304.             goto done;
  305.         }
  306.         else if ((av[i][0] == '-') || (av[i][0] == '/')) {
  307.             (void)fprintf(stderr, "%s: "
  308.                 "Unsupported option '%s'.\n", av[0], av[i]);
  309.             retval = usage();
  310.             goto done;
  311.         }
  312.         else {
  313.             if ((i == 1) && (*av[i] != '-')) {
  314.                 newgrpname = av[i];
  315.                 continue;
  316.             }
  317.  
  318.             cmd_arg_index = i+1;
  319.             st = SHELLTYPE_NONE;
  320.             break;
  321.         }
  322.     }
  323.  
  324.     /*
  325.      * Handle newgrp(1)-like behaviour (run new shell (in our
  326.      * case cmd.exe) with requested group), e.g. ...
  327.      * $ winsg -g cygwingrp1
  328.      * $ winsg cygwingrp1
  329.      */
  330.     if ((st == SHELLTYPE_NOT_SET) && (cmd_arg_index == -1)) {
  331.         st = SHELLTYPE_NONE;
  332.         /* set |cmd_arg_index| to the end of |av|, which is |NULL| */
  333.         cmd_arg_index = i;
  334.     }
  335.  
  336.     if (!newgrpname) {
  337.         (void)fprintf(stderr, "%s: No group name given.\n", av[0]);
  338.         retval = 1;
  339.         goto done;
  340.     }
  341.  
  342.     D((void)fprintf(stderr,
  343.         "# shelltype=%d, cmd_arg_index=%d, "
  344.         "av[cmd_arg_index]='%s', "
  345.         "new group name '%s'\n",
  346.         (int)st, cmd_arg_index, av[cmd_arg_index], newgrpname));
  347.  
  348.     if (!OpenProcessToken(GetCurrentProcess(),
  349.         TOKEN_QUERY|TOKEN_ADJUST_DEFAULT|TOKEN_DUPLICATE,
  350.         &tok)) {
  351.         (void)fprintf(stderr, "%s: Cannot open token.\n", av[0]);
  352.         retval = 1;
  353.         goto done;
  354.     }
  355.  
  356.     D(
  357.         char pgroupname[GNLEN+1];
  358.  
  359.         get_token_primarygroup_name(tok, pgroupname);
  360.         (void)printf("primary group name '%s'\n", pgroupname);
  361.     )
  362.  
  363.     DECLARE_SID_BUFFER(sidbuff);
  364.     PSID pgsid = (PSID)sidbuff;
  365.     DWORD pgsid_size = SECURITY_MAX_SID_SIZE;
  366.  
  367.     if (!get_group_sid(newgrpname, pgsid, &pgsid_size)) {
  368.         (void)fprintf(stderr, "%s: Could not find group '%s'.\n",
  369.             av[0], newgrpname);
  370.         retval = 1;
  371.         goto done;
  372.     }
  373.  
  374.     if (!is_group_in_token(tok, pgsid)) {
  375.         (void)fprintf(stderr, "%s: "
  376.             "Current user is not a member of group '%s'.\n",
  377.             av[0], newgrpname);
  378.         retval = 1;
  379.         goto done;
  380.     }
  381.  
  382.     if (!set_token_primarygroup_sid(tok, pgsid)) {
  383.         (void)fprintf(stderr,
  384.             "%s: Could not switch to new primary group '%s'.\n",
  385.             av[0], newgrpname);
  386.         retval = 1;
  387.         goto done;
  388.     }
  389.  
  390.     D(
  391.         get_token_primarygroup_name(tok, pgroupname);
  392.         (void)printf("primary group name '%s'\n", pgroupname);
  393.     )
  394.  
  395.     (void)_flushall();
  396.  
  397.     retval = 0;
  398.  
  399.     switch(st) {
  400.         case SHELLTYPE_SYSTEM:
  401.             if (av[cmd_arg_index] != NULL) {
  402.                 size_t cmdbuff_size = strlen(CYGWIN_BASH_PATH)+
  403.                     16+
  404.                     strlen(av[cmd_arg_index])*2;
  405.                 char *cmdbuff = alloca(cmdbuff_size);
  406.                 char *s = cmdbuff;
  407.                 s = stpcpy(s, CYGWIN_BASH_PATH);
  408.                 s = stpcpy(s, " -c ");
  409.  
  410.                 win32cmd_quotearg(s, av[cmd_arg_index]);
  411.                 D((void)fprintf(stderr, "# executing '%s'\n", cmdbuff));
  412.                 subcmdret = system(cmdbuff);
  413.             }
  414.             else {
  415.                 subcmdret = system(CYGWIN_BASH_PATH);
  416.             }
  417.             break;
  418.         case SHELLTYPE_CMD:
  419.             if (av[cmd_arg_index] != NULL) {
  420.                 subcmdret = _spawnl(_P_WAIT,
  421.                     WIN32_CMDEXE_PATH, WIN32_CMDEXE_PATH,
  422.                     "/C", av[cmd_arg_index], NULL);
  423.             }
  424.             else {
  425.                 subcmdret = _spawnl(_P_WAIT,
  426.                     WIN32_CMDEXE_PATH, WIN32_CMDEXE_PATH, NULL);
  427.             }
  428.             break;
  429.         case SHELLTYPE_NONE:
  430.             subcmdret = _spawnl(_P_WAIT,
  431.                 WIN32_CMDEXE_PATH, WIN32_CMDEXE_PATH, NULL);
  432.             break;
  433.         default:
  434.             assert(0);
  435.             break;
  436.     }
  437.  
  438.     D((void)fprintf(stdout, "#mark winsg done, subcmdret=%d\n",
  439.         (int)subcmdret));
  440.  
  441. done:
  442.     if (tok == INVALID_HANDLE_VALUE) {
  443.         (void)CloseHandle(tok);
  444.     }
  445.  
  446.     return retval;
  447. }

Submit a correction or amendment below (click here to make a fresh posting)
After submitting an amendment, you'll be able to view the differences between the old and new posts easily.

Syntax highlighting:

To highlight particular lines, prefix each line with {%HIGHLIGHT}




All content is user-submitted.
The administrators of this site (kpaste.net) are not responsible for their content.
Abuse reports should be emailed to us at