mirror of
https://github.com/postgres/postgres.git
synced 2025-11-12 05:01:15 +03:00
When an acl item is added or updated the new entry is deleted if it has no permissions and the acl array is shrinked. This is is done by decrementing the number of items without updating the corresponding array size. The array with the incorrect size is later read by pg_aclcheck and the entry count is used to allocate a new array while the array size is used to copy the old one. This causes a memory corruption and a backend crash. This happens only to normal user as the administrator bypasses acl checks. Massimo Dal Zotto
619 lines
15 KiB
C
619 lines
15 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* acl.c--
|
|
* Basic access control list data structures manipulation routines.
|
|
*
|
|
* Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* $Header: /cvsroot/pgsql/src/backend/utils/adt/acl.c,v 1.8 1996/11/20 22:53:10 momjian Exp $
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
#include "postgres.h"
|
|
|
|
#include <utils/memutils.h>
|
|
#include "utils/acl.h"
|
|
#include "catalog/pg_user.h"
|
|
#include "utils/syscache.h"
|
|
#include "miscadmin.h"
|
|
|
|
static char *getid(char *s, char *n);
|
|
static int32 aclitemeq(AclItem *a1, AclItem *a2);
|
|
static int32 aclitemgt(AclItem *a1, AclItem *a2);
|
|
|
|
#define ACL_IDTYPE_GID_KEYWORD "group"
|
|
#define ACL_IDTYPE_UID_KEYWORD "user"
|
|
|
|
|
|
/*
|
|
* getid
|
|
* Consumes the first alphanumeric string (identifier) found in string
|
|
* 's', ignoring any leading white space.
|
|
*
|
|
* RETURNS:
|
|
* the string position in 's' that points to the next non-space character
|
|
* in 's'. Also:
|
|
* - loads the identifier into 'name'. (If no identifier is found, 'name'
|
|
* contains an empty string).
|
|
*/
|
|
static char *
|
|
getid(char *s, char *n)
|
|
{
|
|
unsigned len;
|
|
char *id;
|
|
|
|
Assert(s && n);
|
|
|
|
while (isspace(*s))
|
|
++s;
|
|
for (id = s, len = 0; isalnum(*s); ++len, ++s)
|
|
;
|
|
if (len > sizeof(NameData))
|
|
elog(WARN, "getid: identifier cannot be >%d characters",
|
|
sizeof(NameData));
|
|
if (len > 0)
|
|
memmove(n, id, len);
|
|
n[len] = '\0';
|
|
while (isspace(*s))
|
|
++s;
|
|
return(s);
|
|
}
|
|
|
|
/*
|
|
* aclparse
|
|
* Consumes and parses an ACL specification of the form:
|
|
* [group|user] [A-Za-z0-9]*[+-=][rwaR]*
|
|
* from string 's', ignoring any leading white space or white space
|
|
* between the optional id type keyword (group|user) and the actual
|
|
* ACL specification.
|
|
*
|
|
* This routine is called by the parser as well as aclitemin(), hence
|
|
* the added generality.
|
|
*
|
|
* RETURNS:
|
|
* the string position in 's' immediately following the ACL
|
|
* specification. Also:
|
|
* - loads the structure pointed to by 'aip' with the appropriate
|
|
* UID/GID, id type identifier and mode type values.
|
|
* - loads 'modechg' with the mode change flag.
|
|
*/
|
|
char *
|
|
aclparse(char *s, AclItem *aip, unsigned *modechg)
|
|
{
|
|
HeapTuple htp;
|
|
char name[NAMEDATALEN+1];
|
|
extern AclId get_grosysid();
|
|
|
|
Assert(s && aip && modechg);
|
|
|
|
aip->ai_idtype = ACL_IDTYPE_UID;
|
|
s = getid(s, name);
|
|
if (*s != ACL_MODECHG_ADD_CHR &&
|
|
*s != ACL_MODECHG_DEL_CHR &&
|
|
*s != ACL_MODECHG_EQL_CHR)
|
|
{ /* we just read a keyword, not a name */
|
|
if (!strcmp(name, ACL_IDTYPE_GID_KEYWORD)) {
|
|
aip->ai_idtype = ACL_IDTYPE_GID;
|
|
} else if (strcmp(name, ACL_IDTYPE_UID_KEYWORD)) {
|
|
elog(WARN, "aclparse: bad keyword, must be [group|user]");
|
|
}
|
|
s = getid(s, name); /* move s to the name beyond the keyword */
|
|
if (name[0] == '\0')
|
|
elog(WARN, "aclparse: a name must follow the [group|user] keyword");
|
|
}
|
|
if (name[0] == '\0')
|
|
aip->ai_idtype = ACL_IDTYPE_WORLD;
|
|
|
|
switch (*s) {
|
|
case ACL_MODECHG_ADD_CHR: *modechg = ACL_MODECHG_ADD; break;
|
|
case ACL_MODECHG_DEL_CHR: *modechg = ACL_MODECHG_DEL; break;
|
|
case ACL_MODECHG_EQL_CHR: *modechg = ACL_MODECHG_EQL; break;
|
|
default: elog(WARN, "aclparse: mode change flag must use \"%s\"",
|
|
ACL_MODECHG_STR);
|
|
}
|
|
|
|
aip->ai_mode = ACL_NO;
|
|
while (isalpha(*++s)) {
|
|
switch (*s) {
|
|
case ACL_MODE_AP_CHR: aip->ai_mode |= ACL_AP; break;
|
|
case ACL_MODE_RD_CHR: aip->ai_mode |= ACL_RD; break;
|
|
case ACL_MODE_WR_CHR: aip->ai_mode |= ACL_WR; break;
|
|
case ACL_MODE_RU_CHR: aip->ai_mode |= ACL_RU; break;
|
|
default: elog(WARN, "aclparse: mode flags must use \"%s\"",
|
|
ACL_MODE_STR);
|
|
}
|
|
}
|
|
|
|
switch (aip->ai_idtype) {
|
|
case ACL_IDTYPE_UID:
|
|
htp = SearchSysCacheTuple(USENAME, PointerGetDatum(name),
|
|
0,0,0);
|
|
if (!HeapTupleIsValid(htp))
|
|
elog(WARN, "aclparse: non-existent user \"%s\"", name);
|
|
aip->ai_id = ((Form_pg_user) GETSTRUCT(htp))->usesysid;
|
|
break;
|
|
case ACL_IDTYPE_GID:
|
|
aip->ai_id = get_grosysid(name);
|
|
break;
|
|
case ACL_IDTYPE_WORLD:
|
|
aip->ai_id = ACL_ID_WORLD;
|
|
break;
|
|
}
|
|
|
|
#ifdef ACLDEBUG_TRACE
|
|
elog(DEBUG, "aclparse: correctly read [%x %d %x], modechg=%x",
|
|
aip->ai_idtype, aip->ai_id, aip->ai_mode, *modechg);
|
|
#endif
|
|
return(s);
|
|
}
|
|
|
|
/*
|
|
* makeacl
|
|
* Allocates storage for a new Acl with 'n' entries.
|
|
*
|
|
* RETURNS:
|
|
* the new Acl
|
|
*/
|
|
Acl *
|
|
makeacl(int n)
|
|
{
|
|
Acl *new_acl;
|
|
Size size;
|
|
|
|
if (n < 0)
|
|
elog(WARN, "makeacl: invalid size: %d\n", n);
|
|
size = ACL_N_SIZE(n);
|
|
if (!(new_acl = (Acl *) palloc(size)))
|
|
elog(WARN, "makeacl: palloc failed on %d\n", size);
|
|
memset((char *) new_acl, 0, size);
|
|
new_acl->size = size;
|
|
new_acl->ndim = 1;
|
|
new_acl->flags = 0;
|
|
ARR_LBOUND(new_acl)[0] = 0;
|
|
ARR_DIMS(new_acl)[0] = n;
|
|
return(new_acl);
|
|
}
|
|
|
|
/*
|
|
* aclitemin
|
|
* Allocates storage for, and fills in, a new AclItem given a string
|
|
* 's' that contains an ACL specification. See aclparse for details.
|
|
*
|
|
* RETURNS:
|
|
* the new AclItem
|
|
*/
|
|
AclItem *
|
|
aclitemin(char *s)
|
|
{
|
|
unsigned modechg;
|
|
AclItem *aip;
|
|
|
|
if (!s)
|
|
elog(WARN, "aclitemin: null string");
|
|
|
|
aip = (AclItem *) palloc(sizeof(AclItem));
|
|
if (!aip)
|
|
elog(WARN, "aclitemin: palloc failed");
|
|
s = aclparse(s, aip, &modechg);
|
|
if (modechg != ACL_MODECHG_EQL)
|
|
elog(WARN, "aclitemin: cannot accept anything but = ACLs");
|
|
while (isspace(*s))
|
|
++s;
|
|
if (*s)
|
|
elog(WARN, "aclitemin: extra garbage at end of specification");
|
|
return(aip);
|
|
}
|
|
|
|
/*
|
|
* aclitemout
|
|
* Allocates storage for, and fills in, a new null-delimited string
|
|
* containing a formatted ACL specification. See aclparse for details.
|
|
*
|
|
* RETURNS:
|
|
* the new string
|
|
*/
|
|
char *
|
|
aclitemout(AclItem *aip)
|
|
{
|
|
register char *p;
|
|
char *out;
|
|
HeapTuple htp;
|
|
unsigned i;
|
|
static AclItem default_aclitem = { ACL_ID_WORLD,
|
|
ACL_IDTYPE_WORLD,
|
|
ACL_WORLD_DEFAULT };
|
|
extern char *int2out();
|
|
char *tmpname;
|
|
|
|
if (!aip)
|
|
aip = &default_aclitem;
|
|
|
|
p = out = palloc(strlen("group =arwR ") + 1 + NAMEDATALEN);
|
|
if (!out)
|
|
elog(WARN, "aclitemout: palloc failed");
|
|
*p = '\0';
|
|
|
|
switch (aip->ai_idtype) {
|
|
case ACL_IDTYPE_UID:
|
|
htp = SearchSysCacheTuple(USESYSID, ObjectIdGetDatum(aip->ai_id),
|
|
0,0,0);
|
|
if (!HeapTupleIsValid(htp)) {
|
|
char *tmp = int2out(aip->ai_id);
|
|
|
|
elog(NOTICE, "aclitemout: usesysid %d not found",
|
|
aip->ai_id);
|
|
(void) strcat(p, tmp);
|
|
pfree(tmp);
|
|
} else
|
|
(void) strncat(p, (char *) &((Form_pg_user)
|
|
GETSTRUCT(htp))->usename,
|
|
sizeof(NameData));
|
|
break;
|
|
case ACL_IDTYPE_GID:
|
|
(void) strcat(p, "group ");
|
|
tmpname = get_groname(aip->ai_id);
|
|
(void) strncat(p, tmpname, NAMEDATALEN);
|
|
break;
|
|
case ACL_IDTYPE_WORLD:
|
|
break;
|
|
default:
|
|
elog(WARN, "aclitemout: bad ai_idtype: %d", aip->ai_idtype);
|
|
break;
|
|
}
|
|
while (*p)
|
|
++p;
|
|
*p++ = '=';
|
|
for (i = 0; i < N_ACL_MODES; ++i)
|
|
if ((aip->ai_mode >> i) & 01)
|
|
*p++ = ACL_MODE_STR[i];
|
|
*p = '\0';
|
|
|
|
return(out);
|
|
}
|
|
|
|
/*
|
|
* aclitemeq
|
|
* aclitemgt
|
|
* AclItem equality and greater-than comparison routines.
|
|
* Two AclItems are equal iff they are both NULL or they have the
|
|
* same identifier (and identifier type).
|
|
*
|
|
* RETURNS:
|
|
* a boolean value indicating = or >
|
|
*/
|
|
static int32
|
|
aclitemeq(AclItem *a1, AclItem *a2)
|
|
{
|
|
if (!a1 && !a2)
|
|
return(1);
|
|
if (!a1 || !a2)
|
|
return(0);
|
|
return(a1->ai_idtype == a2->ai_idtype && a1->ai_id == a2->ai_id);
|
|
}
|
|
|
|
static int32
|
|
aclitemgt(AclItem *a1, AclItem *a2)
|
|
{
|
|
if (a1 && !a2)
|
|
return(1);
|
|
if (!a1 || !a2)
|
|
return(0);
|
|
return((a1->ai_idtype > a2->ai_idtype) ||
|
|
(a1->ai_idtype == a2->ai_idtype && a1->ai_id > a2->ai_id));
|
|
}
|
|
|
|
Acl *
|
|
aclownerdefault(AclId ownerid)
|
|
{
|
|
Acl *acl;
|
|
AclItem *aip;
|
|
|
|
acl = makeacl(2);
|
|
aip = ACL_DAT(acl);
|
|
aip[0].ai_idtype = ACL_IDTYPE_WORLD;
|
|
aip[0].ai_id = ACL_ID_WORLD;
|
|
aip[0].ai_mode = ACL_WORLD_DEFAULT;
|
|
aip[1].ai_idtype = ACL_IDTYPE_UID;
|
|
aip[1].ai_id = ownerid;
|
|
aip[1].ai_mode = ACL_OWNER_DEFAULT;
|
|
return(acl);
|
|
}
|
|
|
|
Acl *
|
|
acldefault(void)
|
|
{
|
|
Acl *acl;
|
|
AclItem *aip;
|
|
|
|
acl = makeacl(1);
|
|
aip = ACL_DAT(acl);
|
|
aip[0].ai_idtype = ACL_IDTYPE_WORLD;
|
|
aip[0].ai_id = ACL_ID_WORLD;
|
|
aip[0].ai_mode = ACL_WORLD_DEFAULT;
|
|
return(acl);
|
|
}
|
|
|
|
Acl *
|
|
aclinsert3(Acl *old_acl, AclItem *mod_aip, unsigned modechg)
|
|
{
|
|
Acl *new_acl;
|
|
AclItem *old_aip, *new_aip;
|
|
unsigned src, dst, num;
|
|
|
|
if (!old_acl || ACL_NUM(old_acl) < 1) {
|
|
new_acl = makeacl(0);
|
|
return(new_acl);
|
|
}
|
|
if (!mod_aip) {
|
|
new_acl = makeacl(ACL_NUM(old_acl));
|
|
memmove((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
|
|
return(new_acl);
|
|
}
|
|
|
|
num = ACL_NUM(old_acl);
|
|
old_aip = ACL_DAT(old_acl);
|
|
|
|
/*
|
|
* Search the ACL for an existing entry for 'id'. If one exists,
|
|
* just modify the entry in-place (well, in the same position, since
|
|
* we actually return a copy); otherwise, insert the new entry in
|
|
* sort-order.
|
|
*/
|
|
/* find the first element not less than the element to be inserted */
|
|
for (dst = 0; dst < num && aclitemgt(mod_aip, old_aip+dst); ++dst)
|
|
;
|
|
if (dst < num && aclitemeq(mod_aip, old_aip+dst)) {
|
|
/* modify in-place */
|
|
new_acl = makeacl(ACL_NUM(old_acl));
|
|
memmove((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
|
|
new_aip = ACL_DAT(new_acl);
|
|
src = dst;
|
|
} else {
|
|
new_acl = makeacl(num + 1);
|
|
new_aip = ACL_DAT(new_acl);
|
|
if (dst == 0) { /* start */
|
|
elog(WARN, "aclinsert3: insertion before world ACL??");
|
|
} else if (dst >= num) { /* end */
|
|
memmove((char *) new_aip,
|
|
(char *) old_aip,
|
|
num * sizeof(AclItem));
|
|
} else { /* middle */
|
|
memmove((char *) new_aip,
|
|
(char *) old_aip,
|
|
dst * sizeof(AclItem));
|
|
memmove((char *) (new_aip+dst+1),
|
|
(char *) (old_aip+dst),
|
|
(num - dst) * sizeof(AclItem));
|
|
}
|
|
new_aip[dst].ai_id = mod_aip->ai_id;
|
|
new_aip[dst].ai_idtype = mod_aip->ai_idtype;
|
|
num++; /* set num to the size of new_acl */
|
|
src = 0; /* world entry */
|
|
}
|
|
switch (modechg) {
|
|
case ACL_MODECHG_ADD: new_aip[dst].ai_mode =
|
|
old_aip[src].ai_mode | mod_aip->ai_mode;
|
|
break;
|
|
case ACL_MODECHG_DEL: new_aip[dst].ai_mode =
|
|
old_aip[src].ai_mode & ~mod_aip->ai_mode;
|
|
break;
|
|
case ACL_MODECHG_EQL: new_aip[dst].ai_mode =
|
|
mod_aip->ai_mode;
|
|
break;
|
|
}
|
|
/* if the newly added entry has no permissions, delete it from
|
|
the list. For example, this helps in removing entries for users who
|
|
no longer exists...*/
|
|
for (dst = 1; dst < num; dst++) {
|
|
if (new_aip[dst].ai_mode == 0) {
|
|
int i;
|
|
for (i=dst+1; i<num; i++) {
|
|
new_aip[i-1].ai_id = new_aip[i].ai_id;
|
|
new_aip[i-1].ai_idtype = new_aip[i].ai_idtype;
|
|
new_aip[i-1].ai_mode = new_aip[i].ai_mode;
|
|
}
|
|
ARR_DIMS(new_acl)[0] = num -1 ;
|
|
/* Adjust also the array size because it is used for memmove */
|
|
ARR_SIZE(new_acl) -= sizeof(AclItem);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return(new_acl);
|
|
}
|
|
|
|
/*
|
|
* aclinsert
|
|
*
|
|
*/
|
|
Acl *
|
|
aclinsert(Acl *old_acl, AclItem *mod_aip)
|
|
{
|
|
return(aclinsert3(old_acl, mod_aip, ACL_MODECHG_EQL));
|
|
}
|
|
|
|
Acl *
|
|
aclremove(Acl *old_acl, AclItem *mod_aip)
|
|
{
|
|
Acl *new_acl;
|
|
AclItem *old_aip, *new_aip;
|
|
unsigned dst, old_num, new_num;
|
|
|
|
if (!old_acl || ACL_NUM(old_acl) < 1) {
|
|
new_acl = makeacl(0);
|
|
return(new_acl);
|
|
}
|
|
if (!mod_aip) {
|
|
new_acl = makeacl(ACL_NUM(old_acl));
|
|
memmove((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
|
|
return(new_acl);
|
|
}
|
|
|
|
old_num = ACL_NUM(old_acl);
|
|
old_aip = ACL_DAT(old_acl);
|
|
|
|
for (dst = 0; dst < old_num && !aclitemeq(mod_aip, old_aip+dst); ++dst)
|
|
;
|
|
if (dst >= old_num) { /* not found or empty */
|
|
new_acl = makeacl(ACL_NUM(old_acl));
|
|
memmove((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
|
|
} else {
|
|
new_num = old_num - 1;
|
|
new_acl = makeacl(ACL_NUM(old_acl) - 1);
|
|
new_aip = ACL_DAT(new_acl);
|
|
if (dst == 0) { /* start */
|
|
elog(WARN, "aclremove: removal of the world ACL??");
|
|
} else if (dst == old_num - 1) {/* end */
|
|
memmove((char *) new_aip,
|
|
(char *) old_aip,
|
|
new_num * sizeof(AclItem));
|
|
} else { /* middle */
|
|
memmove((char *) new_aip,
|
|
(char *) old_aip,
|
|
dst * sizeof(AclItem));
|
|
memmove((char *) (new_aip+dst),
|
|
(char *) (old_aip+dst+1),
|
|
(new_num - dst) * sizeof(AclItem));
|
|
}
|
|
}
|
|
return(new_acl);
|
|
}
|
|
|
|
int32
|
|
aclcontains(Acl *acl, AclItem *aip)
|
|
{
|
|
unsigned i, num;
|
|
AclItem *aidat;
|
|
|
|
if (!acl || !aip || ((num = ACL_NUM(acl)) < 1))
|
|
return(0);
|
|
aidat = ACL_DAT(acl);
|
|
for (i = 0; i < num; ++i)
|
|
if (aclitemeq(aip, aidat+i))
|
|
return(1);
|
|
return(0);
|
|
}
|
|
|
|
/* parser support routines */
|
|
|
|
/*
|
|
* aclmakepriv
|
|
* make a acl privilege string out of an existing privilege string
|
|
* and a new privilege
|
|
*
|
|
* does not add duplicate privileges
|
|
*
|
|
* the CALLER is reponsible for free'ing the string returned
|
|
*/
|
|
|
|
char*
|
|
aclmakepriv(char* old_privlist, char new_priv)
|
|
{
|
|
char* priv;
|
|
int i;
|
|
int l;
|
|
|
|
Assert(strlen(old_privlist)<5);
|
|
priv = malloc(5); /* at most "rwaR" */;
|
|
|
|
if (old_privlist == NULL || old_privlist[0] == '\0') {
|
|
priv[0] = new_priv;
|
|
priv[1] = '\0';
|
|
return priv;
|
|
}
|
|
|
|
strcpy(priv,old_privlist);
|
|
|
|
l = strlen(old_privlist);
|
|
|
|
if (l == 4) { /* can't add any more privileges */
|
|
return priv;
|
|
}
|
|
|
|
/* check to see if the new privilege is already in the old string */
|
|
for (i=0;i<l;i++) {
|
|
if (priv[i] == new_priv)
|
|
break;
|
|
}
|
|
if (i == l) { /* we really have a new privilege*/
|
|
priv[l] = new_priv;
|
|
priv[l+1] = '\0';
|
|
}
|
|
|
|
return priv;
|
|
}
|
|
|
|
/*
|
|
* aclmakeuser
|
|
* user_type must be "A" - all users
|
|
* "G" - group
|
|
* "U" - user
|
|
*
|
|
* concatentates the two strings together with a space in between
|
|
*
|
|
* this routine is used in the parser
|
|
*
|
|
* the CALLER is responsible for freeing the memory allocated
|
|
*/
|
|
|
|
char*
|
|
aclmakeuser(char* user_type, char* user)
|
|
{
|
|
char* user_list;
|
|
|
|
user_list = malloc(strlen(user) + 3);
|
|
sprintf(user_list, "%s %s", user_type, user);
|
|
return user_list;
|
|
}
|
|
|
|
|
|
/*
|
|
* makeAclStmt:
|
|
* this is a helper routine called by the parser
|
|
* create a ChangeAclStmt
|
|
* we take in the privilegs, relation_name_list, and grantee
|
|
* as well as a single character '+' or '-' to indicate grant or revoke
|
|
*
|
|
* returns a new ChangeACLStmt*
|
|
*
|
|
* this routines works by creating a old-style changle acl string and
|
|
* then calling aclparse;
|
|
*/
|
|
|
|
ChangeACLStmt*
|
|
makeAclStmt(char* privileges, List* rel_list, char* grantee,
|
|
char grant_or_revoke)
|
|
{
|
|
ChangeACLStmt *n = makeNode(ChangeACLStmt);
|
|
char str[MAX_PARSE_BUFFER];
|
|
|
|
n->aclitem = (AclItem*)palloc(sizeof(AclItem));
|
|
/* the grantee string is "G <group_name>", "U <user_name>", or "ALL" */
|
|
if (grantee[0] == 'G') /* group permissions */
|
|
{
|
|
sprintf(str,"%s %s%c%s",
|
|
ACL_IDTYPE_GID_KEYWORD,
|
|
grantee+2, grant_or_revoke,privileges);
|
|
}
|
|
else if (grantee[0] == 'U') /* user permission */
|
|
{
|
|
sprintf(str,"%s %s%c%s",
|
|
ACL_IDTYPE_UID_KEYWORD,
|
|
grantee+2, grant_or_revoke,privileges);
|
|
}
|
|
else /* all permission */
|
|
{
|
|
sprintf(str,"%c%s",
|
|
grant_or_revoke,privileges);
|
|
}
|
|
n->relNames = rel_list;
|
|
aclparse(str, n->aclitem, (unsigned*)&n->modechg);
|
|
return n;
|
|
}
|
|
|
|
|